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