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.internal.build.AaptParser; 22 import com.android.ide.eclipse.adt.internal.build.AidlProcessor; 23 import com.android.ide.eclipse.adt.internal.build.Messages; 24 import com.android.ide.eclipse.adt.internal.build.RenderScriptProcessor; 25 import com.android.ide.eclipse.adt.internal.build.SourceProcessor; 26 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; 27 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity; 28 import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; 29 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; 30 import com.android.ide.eclipse.adt.internal.project.FixLaunchConfig; 31 import com.android.ide.eclipse.adt.internal.project.ProjectHelper; 32 import com.android.ide.eclipse.adt.internal.project.XmlErrorHandler.BasicXmlErrorListener; 33 import com.android.ide.eclipse.adt.internal.resources.manager.IdeScanningContext; 34 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; 35 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; 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.ide.eclipse.adt.io.IFolderWrapper; 40 import com.android.io.StreamException; 41 import com.android.sdklib.AndroidVersion; 42 import com.android.sdklib.IAndroidTarget; 43 import com.android.sdklib.SdkConstants; 44 import com.android.sdklib.internal.build.BuildConfigGenerator; 45 import com.android.sdklib.xml.AndroidManifest; 46 import com.android.sdklib.xml.ManifestData; 47 48 import org.eclipse.core.resources.IFile; 49 import org.eclipse.core.resources.IFolder; 50 import org.eclipse.core.resources.IMarker; 51 import org.eclipse.core.resources.IProject; 52 import org.eclipse.core.resources.IResource; 53 import org.eclipse.core.resources.IResourceDelta; 54 import org.eclipse.core.runtime.CoreException; 55 import org.eclipse.core.runtime.IPath; 56 import org.eclipse.core.runtime.IProgressMonitor; 57 import org.eclipse.core.runtime.NullProgressMonitor; 58 import org.eclipse.core.runtime.Path; 59 import org.eclipse.jdt.core.IJavaProject; 60 import org.eclipse.jdt.core.JavaCore; 61 import org.xml.sax.SAXException; 62 63 import java.io.File; 64 import java.io.IOException; 65 import java.util.ArrayList; 66 import java.util.List; 67 import java.util.Map; 68 69 import javax.xml.parsers.ParserConfigurationException; 70 71 /** 72 * Pre Java Compiler. 73 * This incremental builder performs 2 tasks: 74 * <ul> 75 * <li>compiles the resources located in the res/ folder, along with the 76 * AndroidManifest.xml file into the R.java class.</li> 77 * <li>compiles any .aidl files into a corresponding java file.</li> 78 * </ul> 79 * 80 */ 81 public class PreCompilerBuilder 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.PreCompilerBuilder"; //$NON-NLS-1$ 86 87 /** Flag to pass to PreCompiler builder that the build is a release build. 88 */ 89 public final static String RELEASE_REQUESTED = "android.releaseBuild"; //$NON-NLS-1$ 90 91 92 private static final String PROPERTY_PACKAGE = "manifestPackage"; //$NON-NLS-1$ 93 private static final String PROPERTY_COMPILE_RESOURCES = "compileResources"; //$NON-NLS-1$ 94 private static final String PROPERTY_COMPILE_BUILDCONFIG = "createBuildConfig"; //$NON-NLS-1$ 95 private static final String PROPERTY_BUILDCONFIG_MODE = "buildConfigMode"; //$NON-NLS-1$ 96 97 /** 98 * Resource Compile flag. This flag is reset to false after each successful compilation, and 99 * stored in the project persistent properties. This allows the builder to remember its state 100 * when the project is closed/opened. 101 */ 102 private boolean mMustCompileResources = false; 103 private boolean mMustCreateBuildConfig = false; 104 private boolean mLastBuildConfigMode; 105 106 private final List<SourceProcessor> mProcessors = new ArrayList<SourceProcessor>(2); 107 108 /** cache of the java package defined in the manifest */ 109 private String mManifestPackage; 110 111 /** Output folder for generated Java File. Created on the Builder init 112 * @see #startupOnInitialize() 113 */ 114 private IFolder mGenFolder; 115 116 /** 117 * Progress monitor used at the end of every build to refresh the content of the 'gen' folder 118 * and set the generated files as derived. 119 */ 120 private DerivedProgressMonitor mDerivedProgressMonitor; 121 122 123 /** 124 * Progress monitor waiting the end of the process to set a persistent value 125 * in a file. This is typically used in conjunction with <code>IResource.refresh()</code>, 126 * since this call is asynchronous, and we need to wait for it to finish for the file 127 * to be known by eclipse, before we can call <code>resource.setPersistentProperty</code> on 128 * a new file. 129 */ 130 private static class DerivedProgressMonitor implements IProgressMonitor { 131 private boolean mCancelled = false; 132 private boolean mDone = false; 133 private final IFolder mGenFolder; 134 135 public DerivedProgressMonitor(IFolder genFolder) { 136 mGenFolder = genFolder; 137 } 138 139 void reset() { 140 mDone = false; 141 } 142 143 @Override 144 public void beginTask(String name, int totalWork) { 145 } 146 147 @Override 148 public void done() { 149 if (mDone == false) { 150 mDone = true; 151 processChildrenOf(mGenFolder); 152 } 153 } 154 155 private void processChildrenOf(IFolder folder) { 156 IResource[] list; 157 try { 158 list = folder.members(); 159 } catch (CoreException e) { 160 return; 161 } 162 163 for (IResource member : list) { 164 if (member.exists()) { 165 if (member.getType() == IResource.FOLDER) { 166 processChildrenOf((IFolder) member); 167 } 168 169 try { 170 member.setDerived(true, new NullProgressMonitor()); 171 } catch (CoreException e) { 172 // This really shouldn't happen since we check that the resource 173 // exist. 174 // Worst case scenario, the resource isn't marked as derived. 175 } 176 } 177 } 178 } 179 180 @Override 181 public void internalWorked(double work) { 182 } 183 184 @Override 185 public boolean isCanceled() { 186 return mCancelled; 187 } 188 189 @Override 190 public void setCanceled(boolean value) { 191 mCancelled = value; 192 } 193 194 @Override 195 public void setTaskName(String name) { 196 } 197 198 @Override 199 public void subTask(String name) { 200 } 201 202 @Override 203 public void worked(int work) { 204 } 205 } 206 207 public PreCompilerBuilder() { 208 super(); 209 } 210 211 // build() returns a list of project from which this project depends for future compilation. 212 @Override 213 protected IProject[] build( 214 int kind, 215 @SuppressWarnings("rawtypes") Map args, 216 IProgressMonitor monitor) 217 throws CoreException { 218 // get a project object 219 IProject project = getProject(); 220 221 if (DEBUG) { 222 System.out.println("BUILD(PRE) " + project.getName()); 223 } 224 225 // For the PreCompiler, only the library projects are considered Referenced projects, 226 // as only those projects have an impact on what is generated by this builder. 227 IProject[] result = null; 228 229 try { 230 assert mDerivedProgressMonitor != null; 231 232 mDerivedProgressMonitor.reset(); 233 234 // get the project info 235 ProjectState projectState = Sdk.getProjectState(project); 236 237 // this can happen if the project has no project.properties. 238 if (projectState == null) { 239 return null; 240 } 241 242 IAndroidTarget projectTarget = projectState.getTarget(); 243 244 // get the libraries 245 List<IProject> libProjects = projectState.getFullLibraryProjects(); 246 result = libProjects.toArray(new IProject[libProjects.size()]); 247 248 IJavaProject javaProject = JavaCore.create(project); 249 250 // Top level check to make sure the build can move forward. 251 abortOnBadSetup(javaProject); 252 253 // now we need to get the classpath list 254 List<IPath> sourceFolderPathList = BaseProjectHelper.getSourceClasspaths(javaProject); 255 256 PreCompilerDeltaVisitor dv = null; 257 String javaPackage = null; 258 String minSdkVersion = null; 259 260 if (kind == FULL_BUILD) { 261 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, 262 Messages.Start_Full_Pre_Compiler); 263 264 if (DEBUG) { 265 System.out.println("\tfull build!"); 266 } 267 268 // do some clean up. 269 doClean(project, monitor); 270 271 mMustCompileResources = true; 272 mMustCreateBuildConfig = true; 273 274 for (SourceProcessor processor : mProcessors) { 275 processor.prepareFullBuild(project); 276 } 277 } else { 278 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, 279 Messages.Start_Inc_Pre_Compiler); 280 281 // Go through the resources and see if something changed. 282 // Even if the mCompileResources flag is true from a previously aborted 283 // build, we need to go through the Resource delta to get a possible 284 // list of aidl files to compile/remove. 285 IResourceDelta delta = getDelta(project); 286 if (delta == null) { 287 mMustCompileResources = true; 288 289 for (SourceProcessor processor : mProcessors) { 290 processor.prepareFullBuild(project); 291 } 292 } else { 293 dv = new PreCompilerDeltaVisitor(this, sourceFolderPathList, mProcessors); 294 delta.accept(dv); 295 296 // Check to see if Manifest.xml, Manifest.java, or R.java have changed: 297 mMustCompileResources |= dv.getCompileResources(); 298 299 // Notify the ResourceManager: 300 ResourceManager resManager = ResourceManager.getInstance(); 301 ProjectResources projectResources = resManager.getProjectResources(project); 302 303 if (ResourceManager.isAutoBuilding()) { 304 IdeScanningContext context = new IdeScanningContext(projectResources, project); 305 306 resManager.processDelta(delta, context); 307 308 // Check whether this project or its dependencies (libraries) have 309 // resources that need compilation 310 if (context.needsFullAapt()) { 311 mMustCompileResources = true; 312 313 // Must also call markAaptRequested on the project to not just 314 // store "aapt required" on this project, but also on any projects 315 // depending on this project if it's a library project 316 ResourceManager.markAaptRequested(project); 317 } 318 319 // Update error markers in the source editor 320 if (!mMustCompileResources) { 321 context.updateMarkers(false /* async */); 322 } 323 } // else: already processed the deltas in ResourceManager's IRawDeltaListener 324 325 for (SourceProcessor processor : mProcessors) { 326 processor.doneVisiting(project); 327 } 328 329 // get the java package from the visitor 330 javaPackage = dv.getManifestPackage(); 331 minSdkVersion = dv.getMinSdkVersion(); 332 } 333 } 334 335 // Has anyone marked this project as needing aapt? Typically done when 336 // one of the library projects this project depends on has changed 337 mMustCompileResources |= ResourceManager.isAaptRequested(project); 338 339 // store the build status in the persistent storage 340 saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, mMustCompileResources); 341 saveProjectBooleanProperty(PROPERTY_COMPILE_BUILDCONFIG, mMustCreateBuildConfig); 342 343 // if there was some XML errors, we just return w/o doing 344 // anything since we've put some markers in the files anyway. 345 if (dv != null && dv.mXmlError) { 346 AdtPlugin.printErrorToConsole(project, Messages.Xml_Error); 347 348 return result; 349 } 350 351 352 // get the manifest file 353 IFile manifestFile = ProjectHelper.getManifest(project); 354 355 if (manifestFile == null) { 356 String msg = String.format(Messages.s_File_Missing, 357 SdkConstants.FN_ANDROID_MANIFEST_XML); 358 AdtPlugin.printErrorToConsole(project, msg); 359 markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); 360 361 return result; 362 363 // TODO: document whether code below that uses manifest (which is now guaranteed 364 // to be null) will actually be executed or not. 365 } 366 367 // lets check the XML of the manifest first, if that hasn't been done by the 368 // resource delta visitor yet. 369 if (dv == null || dv.getCheckedManifestXml() == false) { 370 BasicXmlErrorListener errorListener = new BasicXmlErrorListener(); 371 try { 372 ManifestData parser = AndroidManifestHelper.parseUnchecked( 373 new IFileWrapper(manifestFile), 374 true /*gather data*/, 375 errorListener); 376 377 if (errorListener.mHasXmlError == true) { 378 // There was an error in the manifest, its file has been marked 379 // by the XmlErrorHandler. The stopBuild() call below will abort 380 // this with an exception. 381 String msg = String.format(Messages.s_Contains_Xml_Error, 382 SdkConstants.FN_ANDROID_MANIFEST_XML); 383 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, msg); 384 markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); 385 386 return result; 387 } 388 389 // Get the java package from the parser. 390 // This can be null if the parsing failed because the resource is out of sync, 391 // in which case the error will already have been logged anyway. 392 if (parser != null) { 393 javaPackage = parser.getPackage(); 394 minSdkVersion = parser.getMinSdkVersionString(); 395 } 396 } catch (StreamException e) { 397 handleStreamException(e); 398 399 return result; 400 } catch (ParserConfigurationException e) { 401 String msg = String.format( 402 "Bad parser configuration for %s: %s", 403 manifestFile.getFullPath(), 404 e.getMessage()); 405 406 handleException(e, msg); 407 return result; 408 409 } catch (SAXException e) { 410 String msg = String.format( 411 "Parser exception for %s: %s", 412 manifestFile.getFullPath(), 413 e.getMessage()); 414 415 handleException(e, msg); 416 return result; 417 } catch (IOException e) { 418 String msg = String.format( 419 "I/O error for %s: %s", 420 manifestFile.getFullPath(), 421 e.getMessage()); 422 423 handleException(e, msg); 424 return result; 425 } 426 } 427 428 int minSdkValue = -1; 429 430 if (minSdkVersion != null) { 431 try { 432 minSdkValue = Integer.parseInt(minSdkVersion); 433 } catch (NumberFormatException e) { 434 // it's ok, it means minSdkVersion contains a (hopefully) valid codename. 435 } 436 437 AndroidVersion targetVersion = projectTarget.getVersion(); 438 439 // remove earlier marker from the manifest 440 removeMarkersFromResource(manifestFile, AdtConstants.MARKER_ADT); 441 442 if (minSdkValue != -1) { 443 String codename = targetVersion.getCodename(); 444 if (codename != null) { 445 // integer minSdk when the target is a preview => fatal error 446 String msg = String.format( 447 "Platform %1$s is a preview and requires application manifest to set %2$s to '%1$s'", 448 codename, AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION); 449 AdtPlugin.printErrorToConsole(project, msg); 450 BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT, 451 msg, IMarker.SEVERITY_ERROR); 452 return result; 453 } else if (minSdkValue > targetVersion.getApiLevel()) { 454 // integer minSdk is too high for the target => warning 455 String msg = String.format( 456 "Attribute %1$s (%2$d) is higher than the project target API level (%3$d)", 457 AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, 458 minSdkValue, targetVersion.getApiLevel()); 459 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, msg); 460 BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT, 461 msg, IMarker.SEVERITY_WARNING); 462 } 463 } else { 464 // looks like the min sdk is a codename, check it matches the codename 465 // of the platform 466 String codename = targetVersion.getCodename(); 467 if (codename == null) { 468 // platform is not a preview => fatal error 469 String msg = String.format( 470 "Manifest attribute '%1$s' is set to '%2$s'. Integer is expected.", 471 AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, minSdkVersion); 472 AdtPlugin.printErrorToConsole(project, msg); 473 BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT, 474 msg, IMarker.SEVERITY_ERROR); 475 return result; 476 } else if (codename.equals(minSdkVersion) == false) { 477 // platform and manifest codenames don't match => fatal error. 478 String msg = String.format( 479 "Value of manifest attribute '%1$s' does not match platform codename '%2$s'", 480 AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, codename); 481 AdtPlugin.printErrorToConsole(project, msg); 482 BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT, 483 msg, IMarker.SEVERITY_ERROR); 484 return result; 485 } 486 487 // if we get there, the minSdkVersion is a codename matching the target 488 // platform codename. In this case we set minSdkValue to the previous API 489 // level, as it's used by source processors. 490 minSdkValue = targetVersion.getApiLevel(); 491 } 492 } else if (projectTarget.getVersion().isPreview()) { 493 // else the minSdkVersion is not set but we are using a preview target. 494 // Display an error 495 String codename = projectTarget.getVersion().getCodename(); 496 String msg = String.format( 497 "Platform %1$s is a preview and requires application manifests to set %2$s to '%1$s'", 498 codename, AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION); 499 AdtPlugin.printErrorToConsole(project, msg); 500 BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT, msg, 501 IMarker.SEVERITY_ERROR); 502 return result; 503 } 504 505 if (javaPackage == null || javaPackage.length() == 0) { 506 // looks like the AndroidManifest file isn't valid. 507 String msg = String.format(Messages.s_Doesnt_Declare_Package_Error, 508 SdkConstants.FN_ANDROID_MANIFEST_XML); 509 AdtPlugin.printErrorToConsole(project, msg); 510 BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT, 511 msg, IMarker.SEVERITY_ERROR); 512 513 return result; 514 } else if (javaPackage.indexOf('.') == -1) { 515 // The application package name does not contain 2+ segments! 516 String msg = String.format( 517 "Application package '%1$s' must have a minimum of 2 segments.", 518 SdkConstants.FN_ANDROID_MANIFEST_XML); 519 AdtPlugin.printErrorToConsole(project, msg); 520 BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT, 521 msg, IMarker.SEVERITY_ERROR); 522 523 return result; 524 } 525 526 // at this point we have the java package. We need to make sure it's not a different 527 // package than the previous one that were built. 528 if (javaPackage.equals(mManifestPackage) == false) { 529 // The manifest package has changed, the user may want to update 530 // the launch configuration 531 if (mManifestPackage != null) { 532 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, 533 Messages.Checking_Package_Change); 534 535 FixLaunchConfig flc = new FixLaunchConfig(project, mManifestPackage, 536 javaPackage); 537 flc.start(); 538 } 539 540 // record the new manifest package, and save it. 541 mManifestPackage = javaPackage; 542 saveProjectStringProperty(PROPERTY_PACKAGE, mManifestPackage); 543 544 // force a clean 545 doClean(project, monitor); 546 mMustCompileResources = true; 547 mMustCreateBuildConfig = true; 548 for (SourceProcessor processor : mProcessors) { 549 processor.prepareFullBuild(project); 550 } 551 552 saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, mMustCompileResources); 553 saveProjectBooleanProperty(PROPERTY_COMPILE_BUILDCONFIG, mMustCreateBuildConfig); 554 } 555 556 try { 557 handleBuildConfig(args); 558 } catch (IOException e) { 559 handleException(e, "Failed to create BuildConfig class"); 560 return result; 561 } 562 563 // run the source processors 564 int processorStatus = SourceProcessor.COMPILE_STATUS_NONE; 565 for (SourceProcessor processor : mProcessors) { 566 try { 567 processorStatus |= processor.compileFiles(this, 568 project, projectTarget, minSdkValue, sourceFolderPathList, monitor); 569 } catch (Throwable t) { 570 handleException(t, String.format( 571 "Failed to run %s. Check workspace log for detail.", 572 processor.getClass().getName())); 573 return result; 574 } 575 } 576 577 // if a processor created some resources file, force recompilation of the resources. 578 if ((processorStatus & SourceProcessor.COMPILE_STATUS_RES) != 0) { 579 mMustCompileResources = true; 580 // save the current state before attempting the compilation 581 saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, mMustCompileResources); 582 } 583 584 // handle the resources, after the processors are run since some (renderscript) 585 // generate resources. 586 boolean compiledTheResources = mMustCompileResources; 587 if (mMustCompileResources) { 588 if (DEBUG) { 589 System.out.println("\tcompiling resources!"); 590 } 591 handleResources(project, javaPackage, projectTarget, manifestFile, libProjects, 592 projectState.isLibrary()); 593 } 594 595 if (processorStatus == SourceProcessor.COMPILE_STATUS_NONE && 596 compiledTheResources == false) { 597 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, 598 Messages.Nothing_To_Compile); 599 } 600 } catch (AbortBuildException e) { 601 return result; 602 } finally { 603 // refresh the 'gen' source folder. Once this is done with the custom progress 604 // monitor to mark all new files as derived 605 mGenFolder.refreshLocal(IResource.DEPTH_INFINITE, mDerivedProgressMonitor); 606 } 607 608 return result; 609 } 610 611 @Override 612 protected void clean(IProgressMonitor monitor) throws CoreException { 613 super.clean(monitor); 614 615 if (DEBUG) { 616 System.out.println("CLEAN(PRE) " + getProject().getName()); 617 } 618 619 doClean(getProject(), monitor); 620 if (mGenFolder != null) { 621 mGenFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor); 622 } 623 } 624 625 private void doClean(IProject project, IProgressMonitor monitor) throws CoreException { 626 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, 627 Messages.Removing_Generated_Classes); 628 629 // remove all the derived resources from the 'gen' source folder. 630 if (mGenFolder != null && mGenFolder.exists()) { 631 // gen folder should not be derived, but previous version could set it to derived 632 // so we make sure this isn't the case (or it'll get deleted by the clean) 633 mGenFolder.setDerived(false, monitor); 634 635 removeDerivedResources(mGenFolder, monitor); 636 } 637 638 // Clear the project of the generic markers 639 removeMarkersFromContainer(project, AdtConstants.MARKER_AAPT_COMPILE); 640 removeMarkersFromContainer(project, AdtConstants.MARKER_XML); 641 removeMarkersFromContainer(project, AdtConstants.MARKER_AIDL); 642 removeMarkersFromContainer(project, AdtConstants.MARKER_RENDERSCRIPT); 643 removeMarkersFromContainer(project, AdtConstants.MARKER_ANDROID); 644 } 645 646 @Override 647 protected void startupOnInitialize() { 648 try { 649 super.startupOnInitialize(); 650 651 IProject project = getProject(); 652 653 // load the previous IFolder and java package. 654 mManifestPackage = loadProjectStringProperty(PROPERTY_PACKAGE); 655 656 // get the source folder in which all the Java files are created 657 mGenFolder = project.getFolder(SdkConstants.FD_GEN_SOURCES); 658 mDerivedProgressMonitor = new DerivedProgressMonitor(mGenFolder); 659 660 // Load the current compile flags. We ask for true if not found to force a recompile. 661 mMustCompileResources = loadProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, true); 662 mMustCreateBuildConfig = loadProjectBooleanProperty(PROPERTY_COMPILE_BUILDCONFIG, true); 663 Boolean v = ProjectHelper.loadBooleanProperty(project, PROPERTY_BUILDCONFIG_MODE); 664 if (v == null) { 665 // no previous build config mode? force regenerate 666 mMustCreateBuildConfig = true; 667 } else { 668 mLastBuildConfigMode = v; 669 } 670 671 672 IJavaProject javaProject = JavaCore.create(project); 673 674 // load the source processors 675 SourceProcessor aidlProcessor = new AidlProcessor(javaProject, mGenFolder); 676 SourceProcessor renderScriptProcessor = new RenderScriptProcessor(javaProject, 677 mGenFolder); 678 mProcessors.add(aidlProcessor); 679 mProcessors.add(renderScriptProcessor); 680 681 } catch (Throwable throwable) { 682 AdtPlugin.log(throwable, "Failed to finish PrecompilerBuilder#startupOnInitialize()"); 683 } 684 } 685 686 private void handleBuildConfig(@SuppressWarnings("rawtypes") Map args) 687 throws IOException, CoreException { 688 boolean debugMode = !args.containsKey(RELEASE_REQUESTED); 689 690 BuildConfigGenerator generator = new BuildConfigGenerator( 691 mGenFolder.getLocation().toOSString(), mManifestPackage, debugMode); 692 693 if (mMustCreateBuildConfig == false) { 694 // check the file is present. 695 IFolder folder = getGenManifestPackageFolder(); 696 if (folder.exists(new Path(BuildConfigGenerator.BUILD_CONFIG_NAME)) == false) { 697 mMustCreateBuildConfig = true; 698 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, getProject(), 699 String.format("Class %1$s is missing!", 700 BuildConfigGenerator.BUILD_CONFIG_NAME)); 701 } else if (debugMode != mLastBuildConfigMode) { 702 // else if the build mode changed, force creation 703 mMustCreateBuildConfig = true; 704 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, getProject(), 705 String.format("Different build mode, must update %1$s!", 706 BuildConfigGenerator.BUILD_CONFIG_NAME)); 707 } 708 } 709 710 if (mMustCreateBuildConfig) { 711 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, getProject(), 712 String.format("Generating %1$s...", BuildConfigGenerator.BUILD_CONFIG_NAME)); 713 generator.generate(); 714 715 mMustCreateBuildConfig = false; 716 saveProjectBooleanProperty(PROPERTY_COMPILE_BUILDCONFIG, mMustCreateBuildConfig); 717 saveProjectBooleanProperty(PROPERTY_BUILDCONFIG_MODE, mLastBuildConfigMode = debugMode); 718 } 719 } 720 721 722 /** 723 * Handles resource changes and regenerate whatever files need regenerating. 724 * @param project the main project 725 * @param javaPackage the app package for the main project 726 * @param projectTarget the target of the main project 727 * @param manifest the {@link IFile} representing the project manifest 728 * @param libProjects the library dependencies 729 * @param isLibrary if the project is a library project 730 * @throws CoreException 731 * @throws AbortBuildException 732 */ 733 private void handleResources(IProject project, String javaPackage, IAndroidTarget projectTarget, 734 IFile manifest, List<IProject> libProjects, boolean isLibrary) 735 throws CoreException, AbortBuildException { 736 // get the resource folder 737 IFolder resFolder = project.getFolder(AdtConstants.WS_RESOURCES); 738 739 // get the file system path 740 IPath outputLocation = mGenFolder.getLocation(); 741 IPath resLocation = resFolder.getLocation(); 742 IPath manifestLocation = manifest == null ? null : manifest.getLocation(); 743 744 // those locations have to exist for us to do something! 745 if (outputLocation != null && resLocation != null 746 && manifestLocation != null) { 747 String osOutputPath = outputLocation.toOSString(); 748 String osResPath = resLocation.toOSString(); 749 String osManifestPath = manifestLocation.toOSString(); 750 751 // remove the aapt markers 752 removeMarkersFromResource(manifest, AdtConstants.MARKER_AAPT_COMPILE); 753 removeMarkersFromContainer(resFolder, AdtConstants.MARKER_AAPT_COMPILE); 754 755 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, 756 Messages.Preparing_Generated_Files); 757 758 // we need to figure out where to store the R class. 759 // get the parent folder for R.java and update mManifestPackageSourceFolder 760 IFolder mainPackageFolder = getGenManifestPackageFolder(); 761 762 // handle libraries 763 ArrayList<IFolder> libResFolders = new ArrayList<IFolder>(); 764 StringBuilder libJavaPackages = null; 765 if (libProjects != null) { 766 for (IProject lib : libProjects) { 767 IFolder libResFolder = lib.getFolder(SdkConstants.FD_RES); 768 if (libResFolder.exists()) { 769 libResFolders.add(libResFolder); 770 } 771 772 try { 773 String libJavaPackage = AndroidManifest.getPackage(new IFolderWrapper(lib)); 774 if (libJavaPackage.equals(javaPackage) == false) { 775 if (libJavaPackages == null) { 776 libJavaPackages = new StringBuilder(libJavaPackage); 777 } else { 778 libJavaPackages.append(":"); 779 libJavaPackages.append(libJavaPackage); 780 } 781 } 782 } catch (Exception e) { 783 } 784 } 785 } 786 787 String libPackages = null; 788 if (libJavaPackages != null) { 789 libPackages = libJavaPackages.toString(); 790 791 } 792 793 execAapt(project, projectTarget, osOutputPath, osResPath, osManifestPath, 794 mainPackageFolder, libResFolders, libPackages, isLibrary); 795 } 796 } 797 798 /** 799 * Executes AAPT to generate R.java/Manifest.java 800 * @param project the main project 801 * @param projectTarget the main project target 802 * @param osOutputPath the OS output path for the generated file. This is the source folder, not 803 * the package folder. 804 * @param osResPath the OS path to the res folder for the main project 805 * @param osManifestPath the OS path to the manifest of the main project 806 * @param packageFolder the IFolder that will contain the generated file. Unlike 807 * <var>osOutputPath</var> this is the direct parent of the generated files. 808 * If <var>customJavaPackage</var> is not null, this must match the new destination triggered 809 * by its value. 810 * @param libResFolders the list of res folders for the library. 811 * @param libraryPackages an optional list of javapackages to replace the main project java package. 812 * can be null. 813 * @param isLibrary if the project is a library project 814 * @throws AbortBuildException 815 */ 816 private void execAapt(IProject project, IAndroidTarget projectTarget, String osOutputPath, 817 String osResPath, String osManifestPath, IFolder packageFolder, 818 ArrayList<IFolder> libResFolders, String libraryPackages, boolean isLibrary) 819 throws AbortBuildException { 820 821 // We actually need to delete the manifest.java as it may become empty and 822 // in this case aapt doesn't generate an empty one, but instead doesn't 823 // touch it. 824 IFile manifestJavaFile = packageFolder.getFile(AdtConstants.FN_MANIFEST_CLASS); 825 manifestJavaFile.getLocation().toFile().delete(); 826 827 // launch aapt: create the command line 828 ArrayList<String> array = new ArrayList<String>(); 829 830 @SuppressWarnings("deprecation") 831 String aaptPath = projectTarget.getPath(IAndroidTarget.AAPT); 832 833 array.add(aaptPath); 834 array.add("package"); //$NON-NLS-1$ 835 array.add("-m"); //$NON-NLS-1$ 836 if (AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE) { 837 array.add("-v"); //$NON-NLS-1$ 838 } 839 840 if (isLibrary) { 841 array.add("--non-constant-id"); //$NON-NLS-1$ 842 } 843 844 if (libResFolders.size() > 0) { 845 array.add("--auto-add-overlay"); //$NON-NLS-1$ 846 } 847 848 // there's no need to generate the R class of the libraries if this is a library too. 849 if (isLibrary == false && libraryPackages != null) { 850 array.add("--extra-packages"); //$NON-NLS-1$ 851 array.add(libraryPackages); 852 } 853 854 array.add("-J"); //$NON-NLS-1$ 855 array.add(osOutputPath); 856 array.add("-M"); //$NON-NLS-1$ 857 array.add(osManifestPath); 858 array.add("-S"); //$NON-NLS-1$ 859 array.add(osResPath); 860 for (IFolder libResFolder : libResFolders) { 861 array.add("-S"); //$NON-NLS-1$ 862 array.add(libResFolder.getLocation().toOSString()); 863 } 864 865 array.add("-I"); //$NON-NLS-1$ 866 array.add(projectTarget.getPath(IAndroidTarget.ANDROID_JAR)); 867 868 if (AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE) { 869 StringBuilder sb = new StringBuilder(); 870 for (String c : array) { 871 sb.append(c); 872 sb.append(' '); 873 } 874 String cmd_line = sb.toString(); 875 AdtPlugin.printToConsole(project, cmd_line); 876 } 877 878 // launch 879 int execError = 1; 880 try { 881 // launch the command line process 882 Process process = Runtime.getRuntime().exec( 883 array.toArray(new String[array.size()])); 884 885 // list to store each line of stderr 886 ArrayList<String> results = new ArrayList<String>(); 887 888 // get the output and return code from the process 889 execError = grabProcessOutput(process, results); 890 891 // attempt to parse the error output 892 boolean parsingError = AaptParser.parseOutput(results, project); 893 894 // if we couldn't parse the output we display it in the console. 895 if (parsingError) { 896 if (execError != 0) { 897 AdtPlugin.printErrorToConsole(project, results.toArray()); 898 } else { 899 AdtPlugin.printBuildToConsole(BuildVerbosity.NORMAL, 900 project, results.toArray()); 901 } 902 } 903 904 if (execError != 0) { 905 // if the exec failed, and we couldn't parse the error output 906 // (and therefore not all files that should have been marked, 907 // were marked), we put a generic marker on the project and abort. 908 if (parsingError) { 909 markProject(AdtConstants.MARKER_ADT, 910 Messages.Unparsed_AAPT_Errors, IMarker.SEVERITY_ERROR); 911 } 912 913 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, 914 Messages.AAPT_Error); 915 916 // abort if exec failed. 917 throw new AbortBuildException(); 918 } 919 } catch (IOException e1) { 920 // something happen while executing the process, 921 // mark the project and exit 922 String msg = String.format(Messages.AAPT_Exec_Error, array.get(0)); 923 markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); 924 925 // Add workaround for the Linux problem described here: 926 // http://developer.android.com/sdk/installing.html#troubleshooting 927 // There are various posts on StackOverflow elsewhere where people are asking 928 // about aapt failing to run, so even though this is documented in the 929 // Troubleshooting section add an error message to help with this 930 // scenario. 931 if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX 932 && System.getProperty("os.arch").endsWith("64") //$NON-NLS-1$ //$NON-NLS-2$ 933 && new File(aaptPath).exists() 934 && new File("/usr/bin/apt-get").exists()) { //$NON-NLS-1$ 935 markProject(AdtConstants.MARKER_ADT, 936 "Hint: On 64-bit systems, make sure the 32-bit libraries are installed: sudo apt-get install ia32-libs", 937 IMarker.SEVERITY_ERROR); 938 // Note - this uses SEVERITY_ERROR even though it's really SEVERITY_INFO because 939 // we want this error message to show up adjacent to the aapt error message 940 // (and Eclipse sorts by priority) 941 } 942 943 // This interrupts the build. 944 throw new AbortBuildException(); 945 } catch (InterruptedException e) { 946 // we got interrupted waiting for the process to end... 947 // mark the project and exit 948 String msg = String.format(Messages.AAPT_Exec_Error, array.get(0)); 949 markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); 950 951 // This interrupts the build. 952 throw new AbortBuildException(); 953 } finally { 954 // we've at least attempted to run aapt, save the fact that we don't have to 955 // run it again, unless there's a new resource change. 956 saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, 957 mMustCompileResources = false); 958 ResourceManager.clearAaptRequest(project); 959 } 960 } 961 962 /** 963 * Creates a relative {@link IPath} from a java package. 964 * @param javaPackageName the java package. 965 */ 966 private IPath getJavaPackagePath(String javaPackageName) { 967 // convert the java package into path 968 String[] segments = javaPackageName.split(AdtConstants.RE_DOT); 969 970 StringBuilder path = new StringBuilder(); 971 for (String s : segments) { 972 path.append(AdtConstants.WS_SEP_CHAR); 973 path.append(s); 974 } 975 976 return new Path(path.toString()); 977 } 978 979 /** 980 * Returns an {@link IFolder} (located inside the 'gen' source folder), that matches the 981 * package defined in the manifest. This {@link IFolder} may not actually exist 982 * (aapt will create it anyway). 983 * @return the {@link IFolder} that will contain the R class or null if 984 * the folder was not found. 985 * @throws CoreException 986 */ 987 private IFolder getGenManifestPackageFolder() throws CoreException { 988 // get the path for the package 989 IPath packagePath = getJavaPackagePath(mManifestPackage); 990 991 // get a folder for this path under the 'gen' source folder, and return it. 992 // This IFolder may not reference an actual existing folder. 993 return mGenFolder.getFolder(packagePath); 994 } 995 } 996