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; 18 19 import com.android.ide.eclipse.adt.AdtPlugin; 20 import com.android.ide.eclipse.adt.AndroidConstants; 21 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; 22 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity; 23 import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; 24 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; 25 import com.android.ide.eclipse.adt.internal.project.FixLaunchConfig; 26 import com.android.ide.eclipse.adt.internal.project.ProjectHelper; 27 import com.android.ide.eclipse.adt.internal.project.XmlErrorHandler.BasicXmlErrorListener; 28 import com.android.ide.eclipse.adt.internal.sdk.ProjectState; 29 import com.android.ide.eclipse.adt.internal.sdk.Sdk; 30 import com.android.ide.eclipse.adt.io.IFileWrapper; 31 import com.android.ide.eclipse.adt.io.IFolderWrapper; 32 import com.android.sdklib.AndroidVersion; 33 import com.android.sdklib.IAndroidTarget; 34 import com.android.sdklib.SdkConstants; 35 import com.android.sdklib.xml.AndroidManifest; 36 import com.android.sdklib.xml.ManifestData; 37 38 import org.eclipse.core.resources.IContainer; 39 import org.eclipse.core.resources.IFile; 40 import org.eclipse.core.resources.IFolder; 41 import org.eclipse.core.resources.IMarker; 42 import org.eclipse.core.resources.IProject; 43 import org.eclipse.core.resources.IResource; 44 import org.eclipse.core.resources.IResourceDelta; 45 import org.eclipse.core.resources.IWorkspaceRoot; 46 import org.eclipse.core.resources.ResourcesPlugin; 47 import org.eclipse.core.runtime.CoreException; 48 import org.eclipse.core.runtime.IPath; 49 import org.eclipse.core.runtime.IProgressMonitor; 50 import org.eclipse.core.runtime.Path; 51 import org.eclipse.core.runtime.SubProgressMonitor; 52 import org.eclipse.jdt.core.IJavaProject; 53 import org.eclipse.jdt.core.JavaCore; 54 55 import java.io.IOException; 56 import java.util.ArrayList; 57 import java.util.Map; 58 import java.util.regex.Matcher; 59 import java.util.regex.Pattern; 60 61 /** 62 * Pre Java Compiler. 63 * This incremental builder performs 2 tasks: 64 * <ul> 65 * <li>compiles the resources located in the res/ folder, along with the 66 * AndroidManifest.xml file into the R.java class.</li> 67 * <li>compiles any .aidl files into a corresponding java file.</li> 68 * </ul> 69 * 70 */ 71 public class PreCompilerBuilder extends BaseBuilder { 72 73 /** This ID is used in plugin.xml and in each project's .project file. 74 * It cannot be changed even if the class is renamed/moved */ 75 public static final String ID = "com.android.ide.eclipse.adt.PreCompilerBuilder"; //$NON-NLS-1$ 76 77 private static final String PROPERTY_PACKAGE = "manifestPackage"; //$NON-NLS-1$ 78 79 private static final String PROPERTY_COMPILE_RESOURCES = "compileResources"; //$NON-NLS-1$ 80 private static final String PROPERTY_COMPILE_AIDL = "compileAidl"; //$NON-NLS-1$ 81 82 /** 83 * Single line aidl error<br> 84 * "<path>:<line>: <error>" 85 * or 86 * "<path>:<line> <error>" 87 */ 88 private static Pattern sAidlPattern1 = Pattern.compile("^(.+?):(\\d+):?\\s(.+)$"); //$NON-NLS-1$ 89 90 /** 91 * Data to temporarly store aidl source file information 92 */ 93 static class AidlData { 94 IFile aidlFile; 95 IFolder sourceFolder; 96 AidlData(IFolder sourceFolder, IFile aidlFile)97 AidlData(IFolder sourceFolder, IFile aidlFile) { 98 this.sourceFolder = sourceFolder; 99 this.aidlFile = aidlFile; 100 } 101 102 @Override equals(Object obj)103 public boolean equals(Object obj) { 104 if (this == obj) { 105 return true; 106 } 107 108 if (obj instanceof AidlData) { 109 AidlData file = (AidlData)obj; 110 return aidlFile.equals(file.aidlFile) && sourceFolder.equals(file.sourceFolder); 111 } 112 113 return false; 114 } 115 } 116 117 /** 118 * Resource Compile flag. This flag is reset to false after each successful compilation, and 119 * stored in the project persistent properties. This allows the builder to remember its state 120 * when the project is closed/opened. 121 */ 122 private boolean mMustCompileResources = false; 123 124 /** List of .aidl files found that are modified or new. */ 125 private final ArrayList<AidlData> mAidlToCompile = new ArrayList<AidlData>(); 126 127 /** List of .aidl files that have been removed. */ 128 private final ArrayList<AidlData> mAidlToRemove = new ArrayList<AidlData>(); 129 130 /** cache of the java package defined in the manifest */ 131 private String mManifestPackage; 132 133 /** Output folder for generated Java File. Created on the Builder init 134 * @see #startupOnInitialize() 135 */ 136 private IFolder mGenFolder; 137 138 /** 139 * Progress monitor used at the end of every build to refresh the content of the 'gen' folder 140 * and set the generated files as derived. 141 */ 142 private DerivedProgressMonitor mDerivedProgressMonitor; 143 144 /** 145 * Progress monitor waiting the end of the process to set a persistent value 146 * in a file. This is typically used in conjunction with <code>IResource.refresh()</code>, 147 * since this call is asysnchronous, and we need to wait for it to finish for the file 148 * to be known by eclipse, before we can call <code>resource.setPersistentProperty</code> on 149 * a new file. 150 */ 151 private static class DerivedProgressMonitor implements IProgressMonitor { 152 private boolean mCancelled = false; 153 private final ArrayList<IFile> mFileList = new ArrayList<IFile>(); 154 private boolean mDone = false; DerivedProgressMonitor()155 public DerivedProgressMonitor() { 156 } 157 addFile(IFile file)158 void addFile(IFile file) { 159 mFileList.add(file); 160 } 161 reset()162 void reset() { 163 mFileList.clear(); 164 mDone = false; 165 } 166 beginTask(String name, int totalWork)167 public void beginTask(String name, int totalWork) { 168 } 169 done()170 public void done() { 171 if (mDone == false) { 172 mDone = true; 173 for (IFile file : mFileList) { 174 if (file.exists()) { 175 try { 176 file.setDerived(true); 177 } catch (CoreException e) { 178 // This really shouldn't happen since we check that the resource exist. 179 // Worst case scenario, the resource isn't marked as derived. 180 } 181 } 182 } 183 } 184 } 185 internalWorked(double work)186 public void internalWorked(double work) { 187 } 188 isCanceled()189 public boolean isCanceled() { 190 return mCancelled; 191 } 192 setCanceled(boolean value)193 public void setCanceled(boolean value) { 194 mCancelled = value; 195 } 196 setTaskName(String name)197 public void setTaskName(String name) { 198 } 199 subTask(String name)200 public void subTask(String name) { 201 } 202 worked(int work)203 public void worked(int work) { 204 } 205 } 206 PreCompilerBuilder()207 public PreCompilerBuilder() { 208 super(); 209 } 210 211 // build() returns a list of project from which this project depends for future compilation. 212 @SuppressWarnings("unchecked") 213 @Override build(int kind, Map args, IProgressMonitor monitor)214 protected IProject[] build(int kind, Map args, IProgressMonitor monitor) 215 throws CoreException { 216 // get a project object 217 IProject project = getProject(); 218 219 // list of referenced projects. 220 IProject[] libProjects = null; 221 222 try { 223 mDerivedProgressMonitor.reset(); 224 225 // get the project info 226 ProjectState projectState = Sdk.getProjectState(project); 227 228 // this can happen if the project has no default.properties. 229 if (projectState == null) { 230 return null; 231 } 232 233 IAndroidTarget projectTarget = projectState.getTarget(); 234 235 // get the libraries 236 libProjects = projectState.getFullLibraryProjects(); 237 238 IJavaProject javaProject = JavaCore.create(project); 239 240 // Top level check to make sure the build can move forward. 241 abortOnBadSetup(javaProject); 242 243 // now we need to get the classpath list 244 ArrayList<IPath> sourceFolderPathList = BaseProjectHelper.getSourceClasspaths( 245 javaProject); 246 247 PreCompilerDeltaVisitor dv = null; 248 String javaPackage = null; 249 String minSdkVersion = null; 250 251 if (kind == FULL_BUILD) { 252 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, 253 Messages.Start_Full_Pre_Compiler); 254 255 // do some clean up. 256 doClean(project, monitor); 257 258 mMustCompileResources = true; 259 buildAidlCompilationList(project, sourceFolderPathList); 260 } else { 261 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, 262 Messages.Start_Inc_Pre_Compiler); 263 264 // Go through the resources and see if something changed. 265 // Even if the mCompileResources flag is true from a previously aborted 266 // build, we need to go through the Resource delta to get a possible 267 // list of aidl files to compile/remove. 268 IResourceDelta delta = getDelta(project); 269 if (delta == null) { 270 mMustCompileResources = true; 271 buildAidlCompilationList(project, sourceFolderPathList); 272 } else { 273 dv = new PreCompilerDeltaVisitor(this, sourceFolderPathList); 274 delta.accept(dv); 275 276 // record the state 277 mMustCompileResources |= dv.getCompileResources(); 278 279 if (dv.getForceAidlCompile()) { 280 buildAidlCompilationList(project, sourceFolderPathList); 281 } else { 282 // handle aidl modification, and update mMustCompileAidl 283 mergeAidlFileModifications(dv.getAidlToCompile(), 284 dv.getAidlToRemove()); 285 } 286 287 // get the java package from the visitor 288 javaPackage = dv.getManifestPackage(); 289 minSdkVersion = dv.getMinSdkVersion(); 290 291 // if the main resources didn't change, then we check for the library 292 // ones (will trigger resource recompilation too) 293 if (mMustCompileResources == false && libProjects.length > 0) { 294 for (IProject libProject : libProjects) { 295 delta = getDelta(libProject); 296 if (delta != null) { 297 LibraryDeltaVisitor visitor = new LibraryDeltaVisitor(); 298 delta.accept(visitor); 299 300 mMustCompileResources = visitor.getResChange(); 301 302 if (mMustCompileResources) { 303 break; 304 } 305 } 306 } 307 } 308 } 309 } 310 311 // store the build status in the persistent storage 312 saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES , mMustCompileResources); 313 314 // if there was some XML errors, we just return w/o doing 315 // anything since we've put some markers in the files anyway. 316 if (dv != null && dv.mXmlError) { 317 AdtPlugin.printErrorToConsole(project, Messages.Xml_Error); 318 319 // This interrupts the build. The next builders will not run. 320 stopBuild(Messages.Xml_Error); 321 } 322 323 324 // get the manifest file 325 IFile manifestFile = ProjectHelper.getManifest(project); 326 327 if (manifestFile == null) { 328 String msg = String.format(Messages.s_File_Missing, 329 SdkConstants.FN_ANDROID_MANIFEST_XML); 330 AdtPlugin.printErrorToConsole(project, msg); 331 markProject(AndroidConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); 332 333 // This interrupts the build. The next builders will not run. 334 stopBuild(msg); 335 336 // TODO: document whether code below that uses manifest (which is now guaranteed 337 // to be null) will actually be executed or not. 338 } 339 340 // lets check the XML of the manifest first, if that hasn't been done by the 341 // resource delta visitor yet. 342 if (dv == null || dv.getCheckedManifestXml() == false) { 343 BasicXmlErrorListener errorListener = new BasicXmlErrorListener(); 344 ManifestData parser = AndroidManifestHelper.parse(new IFileWrapper(manifestFile), 345 true /*gather data*/, 346 errorListener); 347 348 if (errorListener.mHasXmlError == true) { 349 // there was an error in the manifest, its file has been marked, 350 // by the XmlErrorHandler. 351 // We return; 352 String msg = String.format(Messages.s_Contains_Xml_Error, 353 SdkConstants.FN_ANDROID_MANIFEST_XML); 354 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, msg); 355 356 // This interrupts the build. The next builders will not run. 357 stopBuild(msg); 358 } 359 360 // get the java package from the parser 361 javaPackage = parser.getPackage(); 362 minSdkVersion = parser.getMinSdkVersionString(); 363 } 364 365 if (minSdkVersion != null) { 366 int minSdkValue = -1; 367 try { 368 minSdkValue = Integer.parseInt(minSdkVersion); 369 } catch (NumberFormatException e) { 370 // it's ok, it means minSdkVersion contains a (hopefully) valid codename. 371 } 372 373 AndroidVersion projectVersion = projectTarget.getVersion(); 374 375 // remove earlier marker from the manifest 376 removeMarkersFromFile(manifestFile, AndroidConstants.MARKER_ADT); 377 378 if (minSdkValue != -1) { 379 String codename = projectVersion.getCodename(); 380 if (codename != null) { 381 // integer minSdk when the target is a preview => fatal error 382 String msg = String.format( 383 "Platform %1$s is a preview and requires appication manifest to set %2$s to '%1$s'", 384 codename, AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION); 385 AdtPlugin.printErrorToConsole(project, msg); 386 BaseProjectHelper.markResource(manifestFile, AndroidConstants.MARKER_ADT, 387 msg, IMarker.SEVERITY_ERROR); 388 stopBuild(msg); 389 } else if (minSdkValue < projectVersion.getApiLevel()) { 390 // integer minSdk is not high enough for the target => warning 391 String msg = String.format( 392 "Attribute %1$s (%2$d) is lower than the project target API level (%3$d)", 393 AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, 394 minSdkValue, projectVersion.getApiLevel()); 395 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, msg); 396 BaseProjectHelper.markResource(manifestFile, AndroidConstants.MARKER_ADT, 397 msg, IMarker.SEVERITY_WARNING); 398 } else if (minSdkValue > projectVersion.getApiLevel()) { 399 // integer minSdk is too high for the target => warning 400 String msg = String.format( 401 "Attribute %1$s (%2$d) is higher than the project target API level (%3$d)", 402 AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, 403 minSdkValue, projectVersion.getApiLevel()); 404 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, msg); 405 BaseProjectHelper.markResource(manifestFile, AndroidConstants.MARKER_ADT, 406 msg, IMarker.SEVERITY_WARNING); 407 } 408 } else { 409 // looks like the min sdk is a codename, check it matches the codename 410 // of the platform 411 String codename = projectVersion.getCodename(); 412 if (codename == null) { 413 // platform is not a preview => fatal error 414 String msg = String.format( 415 "Manifest attribute '%1$s' is set to '%2$s'. Integer is expected.", 416 AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, minSdkVersion); 417 AdtPlugin.printErrorToConsole(project, msg); 418 BaseProjectHelper.markResource(manifestFile, AndroidConstants.MARKER_ADT, 419 msg, IMarker.SEVERITY_ERROR); 420 stopBuild(msg); 421 } else if (codename.equals(minSdkVersion) == false) { 422 // platform and manifest codenames don't match => fatal error. 423 String msg = String.format( 424 "Value of manifest attribute '%1$s' does not match platform codename '%2$s'", 425 AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, codename); 426 AdtPlugin.printErrorToConsole(project, msg); 427 BaseProjectHelper.markResource(manifestFile, AndroidConstants.MARKER_ADT, 428 msg, IMarker.SEVERITY_ERROR); 429 stopBuild(msg); 430 } 431 } 432 } else if (projectTarget.getVersion().isPreview()) { 433 // else the minSdkVersion is not set but we are using a preview target. 434 // Display an error 435 String codename = projectTarget.getVersion().getCodename(); 436 String msg = String.format( 437 "Platform %1$s is a preview and requires appication manifests to set %2$s to '%1$s'", 438 codename, AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION); 439 AdtPlugin.printErrorToConsole(project, msg); 440 BaseProjectHelper.markResource(manifestFile, AndroidConstants.MARKER_ADT, msg, 441 IMarker.SEVERITY_ERROR); 442 stopBuild(msg); 443 } 444 445 if (javaPackage == null || javaPackage.length() == 0) { 446 // looks like the AndroidManifest file isn't valid. 447 String msg = String.format(Messages.s_Doesnt_Declare_Package_Error, 448 SdkConstants.FN_ANDROID_MANIFEST_XML); 449 AdtPlugin.printErrorToConsole(project, msg); 450 BaseProjectHelper.markResource(manifestFile, AndroidConstants.MARKER_ADT, 451 msg, IMarker.SEVERITY_ERROR); 452 453 // This interrupts the build. The next builders will not run. 454 // This also throws an exception and nothing beyond this line will run. 455 stopBuild(msg); 456 } else if (javaPackage.indexOf('.') == -1) { 457 // The application package name does not contain 2+ segments! 458 String msg = String.format( 459 "Application package '%1$s' must have a minimum of 2 segments.", 460 SdkConstants.FN_ANDROID_MANIFEST_XML); 461 AdtPlugin.printErrorToConsole(project, msg); 462 BaseProjectHelper.markResource(manifestFile, AndroidConstants.MARKER_ADT, 463 msg, IMarker.SEVERITY_ERROR); 464 465 // This interrupts the build. The next builders will not run. 466 // This also throws an exception and nothing beyond this line will run. 467 stopBuild(msg); 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 buildAidlCompilationList(project, sourceFolderPathList); 492 493 saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES , mMustCompileResources); 494 } 495 496 if (mMustCompileResources) { 497 handleResources(project, javaPackage, projectTarget, manifestFile, libProjects); 498 } 499 500 // now handle the aidl stuff. 501 boolean aidlStatus = handleAidl(projectTarget, sourceFolderPathList, monitor); 502 503 if (aidlStatus == false && mMustCompileResources == false) { 504 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, 505 Messages.Nothing_To_Compile); 506 } 507 } finally { 508 // refresh the 'gen' source folder. Once this is done with the custom progress 509 // monitor to mark all new files as derived 510 mGenFolder.refreshLocal(IResource.DEPTH_INFINITE, mDerivedProgressMonitor); 511 } 512 513 return libProjects; 514 } 515 516 @Override clean(IProgressMonitor monitor)517 protected void clean(IProgressMonitor monitor) throws CoreException { 518 super.clean(monitor); 519 520 doClean(getProject(), monitor); 521 if (mGenFolder != null) { 522 mGenFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor); 523 } 524 } 525 doClean(IProject project, IProgressMonitor monitor)526 private void doClean(IProject project, IProgressMonitor monitor) throws CoreException { 527 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, 528 Messages.Removing_Generated_Classes); 529 530 // remove all the derived resources from the 'gen' source folder. 531 if (mGenFolder != null) { 532 removeDerivedResources(mGenFolder, monitor); 533 } 534 535 // Clear the project of the generic markers 536 removeMarkersFromProject(project, AndroidConstants.MARKER_AAPT_COMPILE); 537 removeMarkersFromProject(project, AndroidConstants.MARKER_XML); 538 removeMarkersFromProject(project, AndroidConstants.MARKER_AIDL); 539 540 } 541 542 @Override startupOnInitialize()543 protected void startupOnInitialize() { 544 super.startupOnInitialize(); 545 546 mDerivedProgressMonitor = new DerivedProgressMonitor(); 547 548 IProject project = getProject(); 549 550 // load the previous IFolder and java package. 551 mManifestPackage = loadProjectStringProperty(PROPERTY_PACKAGE); 552 553 // get the source folder in which all the Java files are created 554 mGenFolder = project.getFolder(SdkConstants.FD_GEN_SOURCES); 555 556 // Load the current compile flags. We ask for true if not found to force a 557 // recompile. 558 mMustCompileResources = loadProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, true); 559 boolean mustCompileAidl = loadProjectBooleanProperty(PROPERTY_COMPILE_AIDL, true); 560 561 // if we stored that we have to compile some aidl, we build the list that will compile them 562 // all 563 if (mustCompileAidl) { 564 IJavaProject javaProject = JavaCore.create(project); 565 ArrayList<IPath> sourceFolderPathList = BaseProjectHelper.getSourceClasspaths( 566 javaProject); 567 568 buildAidlCompilationList(project, sourceFolderPathList); 569 } 570 } 571 572 /** 573 * Handles resource changes and regenerate whatever files need regenerating. 574 * @param project the main project 575 * @param javaPackage the app package for the main project 576 * @param projectTarget the target of the main project 577 * @param manifest the {@link IFile} representing the project manifest 578 * @param libProjects the library dependencies 579 * @throws CoreException 580 */ handleResources(IProject project, String javaPackage, IAndroidTarget projectTarget, IFile manifest, IProject[] libProjects)581 private void handleResources(IProject project, String javaPackage, IAndroidTarget projectTarget, 582 IFile manifest, IProject[] libProjects) throws CoreException { 583 // get the resource folder 584 IFolder resFolder = project.getFolder(AndroidConstants.WS_RESOURCES); 585 586 // get the file system path 587 IPath outputLocation = mGenFolder.getLocation(); 588 IPath resLocation = resFolder.getLocation(); 589 IPath manifestLocation = manifest == null ? null : manifest.getLocation(); 590 591 // those locations have to exist for us to do something! 592 if (outputLocation != null && resLocation != null 593 && manifestLocation != null) { 594 String osOutputPath = outputLocation.toOSString(); 595 String osResPath = resLocation.toOSString(); 596 String osManifestPath = manifestLocation.toOSString(); 597 598 // remove the aapt markers 599 removeMarkersFromFile(manifest, AndroidConstants.MARKER_AAPT_COMPILE); 600 removeMarkersFromContainer(resFolder, AndroidConstants.MARKER_AAPT_COMPILE); 601 602 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, 603 Messages.Preparing_Generated_Files); 604 605 // we need to figure out where to store the R class. 606 // get the parent folder for R.java and update mManifestPackageSourceFolder 607 IFolder mainPackageFolder = getGenManifestPackageFolder(); 608 609 // handle libraries 610 ArrayList<IFolder> libResFolders = new ArrayList<IFolder>(); 611 ArrayList<IFolder> libOutputFolders = new ArrayList<IFolder>(); 612 ArrayList<String> libJavaPackages = new ArrayList<String>(); 613 if (libProjects != null) { 614 for (IProject lib : libProjects) { 615 IFolder libResFolder = lib.getFolder(SdkConstants.FD_RES); 616 if (libResFolder.exists()) { 617 libResFolders.add(libResFolder); 618 } 619 620 try { 621 String libJavaPackage = AndroidManifest.getPackage(new IFolderWrapper(lib)); 622 if (libJavaPackage.equals(javaPackage) == false) { 623 libJavaPackages.add(libJavaPackage); 624 libOutputFolders.add(getGenManifestPackageFolder(libJavaPackage)); 625 } 626 } catch (Exception e) { 627 } 628 } 629 } 630 631 execAapt(project, projectTarget, osOutputPath, osResPath, osManifestPath, 632 mainPackageFolder, libResFolders, null /* custom java package */); 633 634 final int count = libOutputFolders.size(); 635 if (count > 0) { 636 for (int i = 0 ; i < count ; i++) { 637 IFolder libFolder = libOutputFolders.get(i); 638 String libJavaPackage = libJavaPackages.get(i); 639 execAapt(project, projectTarget, osOutputPath, osResPath, osManifestPath, 640 libFolder, libResFolders, libJavaPackage); 641 } 642 } 643 } 644 } 645 646 /** 647 * Executes AAPT to generate R.java/Manifest.java 648 * @param project the main project 649 * @param projectTarget the main project target 650 * @param osOutputPath the OS output path for the generated file. This is the source folder, not 651 * the package folder. 652 * @param osResPath the OS path to the res folder for the main project 653 * @param osManifestPath the OS path to the manifest of the main project 654 * @param packageFolder the IFolder that will contain the generated file. Unlike 655 * <var>osOutputPath</var> this is the direct parent of the geenerated files. 656 * If <var>customJavaPackage</var> is not null, this must match the new destination triggered 657 * by its value. 658 * @param libResFolders the list of res folders for the library. 659 * @param customJavaPackage an optional javapackage to replace the main project java package. 660 * can be null. 661 * @throws CoreException 662 */ execAapt(IProject project, IAndroidTarget projectTarget, String osOutputPath, String osResPath, String osManifestPath, IFolder packageFolder, ArrayList<IFolder> libResFolders, String customJavaPackage)663 private void execAapt(IProject project, IAndroidTarget projectTarget, String osOutputPath, 664 String osResPath, String osManifestPath, IFolder packageFolder, 665 ArrayList<IFolder> libResFolders, String customJavaPackage) throws CoreException { 666 // since the R.java file may be already existing in read-only 667 // mode we need to make it readable so that aapt can overwrite it 668 IFile rJavaFile = packageFolder.getFile(AndroidConstants.FN_RESOURCE_CLASS); 669 670 // do the same for the Manifest.java class 671 IFile manifestJavaFile = packageFolder.getFile(AndroidConstants.FN_MANIFEST_CLASS); 672 673 // we actually need to delete the manifest.java as it may become empty and 674 // in this case aapt doesn't generate an empty one, but instead doesn't 675 // touch it. 676 manifestJavaFile.getLocation().toFile().delete(); 677 678 // launch aapt: create the command line 679 ArrayList<String> array = new ArrayList<String>(); 680 array.add(projectTarget.getPath(IAndroidTarget.AAPT)); 681 array.add("package"); //$NON-NLS-1$ 682 array.add("-m"); //$NON-NLS-1$ 683 if (AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE) { 684 array.add("-v"); //$NON-NLS-1$ 685 } 686 687 if (libResFolders.size() > 0) { 688 array.add("--auto-add-overlay"); //$NON-NLS-1$ 689 } 690 691 if (customJavaPackage != null) { 692 array.add("--custom-package"); //$NON-NLS-1$ 693 array.add(customJavaPackage); 694 } 695 696 array.add("-J"); //$NON-NLS-1$ 697 array.add(osOutputPath); 698 array.add("-M"); //$NON-NLS-1$ 699 array.add(osManifestPath); 700 array.add("-S"); //$NON-NLS-1$ 701 array.add(osResPath); 702 for (IFolder libResFolder : libResFolders) { 703 array.add("-S"); //$NON-NLS-1$ 704 array.add(libResFolder.getLocation().toOSString()); 705 } 706 707 array.add("-I"); //$NON-NLS-1$ 708 array.add(projectTarget.getPath(IAndroidTarget.ANDROID_JAR)); 709 710 if (AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE) { 711 StringBuilder sb = new StringBuilder(); 712 for (String c : array) { 713 sb.append(c); 714 sb.append(' '); 715 } 716 String cmd_line = sb.toString(); 717 AdtPlugin.printToConsole(project, cmd_line); 718 } 719 720 // launch 721 int execError = 1; 722 try { 723 // launch the command line process 724 Process process = Runtime.getRuntime().exec( 725 array.toArray(new String[array.size()])); 726 727 // list to store each line of stderr 728 ArrayList<String> results = new ArrayList<String>(); 729 730 // get the output and return code from the process 731 execError = grabProcessOutput(process, results); 732 733 // attempt to parse the error output 734 boolean parsingError = AaptParser.parseOutput(results, project); 735 736 // if we couldn't parse the output we display it in the console. 737 if (parsingError) { 738 if (execError != 0) { 739 AdtPlugin.printErrorToConsole(project, results.toArray()); 740 } else { 741 AdtPlugin.printBuildToConsole(BuildVerbosity.NORMAL, 742 project, results.toArray()); 743 } 744 } 745 746 if (execError != 0) { 747 // if the exec failed, and we couldn't parse the error output 748 // (and therefore not all files that should have been marked, 749 // were marked), we put a generic marker on the project and abort. 750 if (parsingError) { 751 markProject(AndroidConstants.MARKER_ADT, 752 Messages.Unparsed_AAPT_Errors, IMarker.SEVERITY_ERROR); 753 } 754 755 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, 756 Messages.AAPT_Error); 757 758 // abort if exec failed. 759 // This interrupts the build. The next builders will not run. 760 stopBuild(Messages.AAPT_Error); 761 } 762 } catch (IOException e1) { 763 // something happen while executing the process, 764 // mark the project and exit 765 String msg = String.format(Messages.AAPT_Exec_Error, array.get(0)); 766 markProject(AndroidConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); 767 768 // This interrupts the build. The next builders will not run. 769 stopBuild(msg); 770 } catch (InterruptedException e) { 771 // we got interrupted waiting for the process to end... 772 // mark the project and exit 773 String msg = String.format(Messages.AAPT_Exec_Error, array.get(0)); 774 markProject(AndroidConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); 775 776 // This interrupts the build. The next builders will not run. 777 stopBuild(msg); 778 } 779 780 // if the return code was OK, we refresh the folder that 781 // contains R.java to force a java recompile. 782 if (execError == 0) { 783 // now add the R.java/Manifest.java to the list of file to be marked 784 // as derived. 785 mDerivedProgressMonitor.addFile(rJavaFile); 786 mDerivedProgressMonitor.addFile(manifestJavaFile); 787 788 // build has been done. reset the state of the builder 789 mMustCompileResources = false; 790 791 // and store it 792 saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, 793 mMustCompileResources); 794 } 795 } 796 797 /** 798 * Creates a relative {@link IPath} from a java package. 799 * @param javaPackageName the java package. 800 */ getJavaPackagePath(String javaPackageName)801 private IPath getJavaPackagePath(String javaPackageName) { 802 // convert the java package into path 803 String[] segments = javaPackageName.split(AndroidConstants.RE_DOT); 804 805 StringBuilder path = new StringBuilder(); 806 for (String s : segments) { 807 path.append(AndroidConstants.WS_SEP_CHAR); 808 path.append(s); 809 } 810 811 return new Path(path.toString()); 812 } 813 814 /** 815 * Returns an {@link IFolder} (located inside the 'gen' source folder), that matches the 816 * package defined in the manifest. This {@link IFolder} may not actually exist 817 * (aapt will create it anyway). 818 * @return the {@link IFolder} that will contain the R class or null if 819 * the folder was not found. 820 * @throws CoreException 821 */ getGenManifestPackageFolder()822 private IFolder getGenManifestPackageFolder() throws CoreException { 823 // get the path for the package 824 IPath packagePath = getJavaPackagePath(mManifestPackage); 825 826 // get a folder for this path under the 'gen' source folder, and return it. 827 // This IFolder may not reference an actual existing folder. 828 return mGenFolder.getFolder(packagePath); 829 } 830 831 /** 832 * Returns an {@link IFolder} (located inside the 'gen' source folder), that matches the 833 * given package. This {@link IFolder} may not actually exist 834 * (aapt will create it anyway). 835 * @param javaPackage the java package that must match the folder. 836 * @return the {@link IFolder} that will contain the R class or null if 837 * the folder was not found. 838 * @throws CoreException 839 */ getGenManifestPackageFolder(String javaPackage)840 private IFolder getGenManifestPackageFolder(String javaPackage) throws CoreException { 841 // get the path for the package 842 IPath packagePath = getJavaPackagePath(javaPackage); 843 844 // get a folder for this path under the 'gen' source folder, and return it. 845 // This IFolder may not reference an actual existing folder. 846 return mGenFolder.getFolder(packagePath); 847 } 848 849 /** 850 * Compiles aidl files into java. This will also removes old java files 851 * created from aidl files that are now gone. 852 * @param projectTarget Target of the project 853 * @param sourceFolders the list of source folders, relative to the workspace. 854 * @param monitor the projess monitor 855 * @returns true if it did something 856 * @throws CoreException 857 */ handleAidl(IAndroidTarget projectTarget, ArrayList<IPath> sourceFolders, IProgressMonitor monitor)858 private boolean handleAidl(IAndroidTarget projectTarget, ArrayList<IPath> sourceFolders, 859 IProgressMonitor monitor) throws CoreException { 860 if (mAidlToCompile.size() == 0 && mAidlToRemove.size() == 0) { 861 return false; 862 } 863 864 // create the command line 865 String[] command = new String[4 + sourceFolders.size()]; 866 int index = 0; 867 command[index++] = projectTarget.getPath(IAndroidTarget.AIDL); 868 command[index++] = "-p" + Sdk.getCurrent().getTarget(getProject()).getPath( //$NON-NLS-1$ 869 IAndroidTarget.ANDROID_AIDL); 870 871 // since the path are relative to the workspace and not the project itself, we need 872 // the workspace root. 873 IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); 874 for (IPath p : sourceFolders) { 875 IFolder f = wsRoot.getFolder(p); 876 command[index++] = "-I" + f.getLocation().toOSString(); //$NON-NLS-1$ 877 } 878 879 // list of files that have failed compilation. 880 ArrayList<AidlData> stillNeedCompilation = new ArrayList<AidlData>(); 881 882 // if an aidl file is being removed before we managed to compile it, it'll be in 883 // both list. We *need* to remove it from the compile list or it'll never go away. 884 for (AidlData aidlFile : mAidlToRemove) { 885 int pos = mAidlToCompile.indexOf(aidlFile); 886 if (pos != -1) { 887 mAidlToCompile.remove(pos); 888 } 889 } 890 891 // loop until we've compile them all 892 for (AidlData aidlData : mAidlToCompile) { 893 // Remove the AIDL error markers from the aidl file 894 removeMarkersFromFile(aidlData.aidlFile, AndroidConstants.MARKER_AIDL); 895 896 // get the path of the source file. 897 IPath sourcePath = aidlData.aidlFile.getLocation(); 898 String osSourcePath = sourcePath.toOSString(); 899 900 IFile javaFile = getGenDestinationFile(aidlData, true /*createFolders*/, monitor); 901 902 // finish to set the command line. 903 command[index] = osSourcePath; 904 command[index + 1] = javaFile.getLocation().toOSString(); 905 906 // launch the process 907 if (execAidl(command, aidlData.aidlFile) == false) { 908 // aidl failed. File should be marked. We add the file to the list 909 // of file that will need compilation again. 910 stillNeedCompilation.add(aidlData); 911 912 // and we move on to the next one. 913 continue; 914 } else { 915 // make sure the file will be marked as derived once we refresh the 'gen' source 916 // folder. 917 mDerivedProgressMonitor.addFile(javaFile); 918 } 919 } 920 921 // change the list to only contains the file that have failed compilation 922 mAidlToCompile.clear(); 923 mAidlToCompile.addAll(stillNeedCompilation); 924 925 // Remove the java files created from aidl files that have been removed. 926 for (AidlData aidlData : mAidlToRemove) { 927 IFile javaFile = getGenDestinationFile(aidlData, false /*createFolders*/, monitor); 928 if (javaFile.exists()) { 929 // This confirms the java file was generated by the builder, 930 // we can delete the aidlFile. 931 javaFile.getLocation().toFile().delete(); 932 } 933 } 934 935 mAidlToRemove.clear(); 936 937 // store the build state. If there are any files that failed to compile, we will 938 // force a full aidl compile on the next project open. (unless a full compilation succeed 939 // before the project is closed/re-opened.) 940 // TODO: Optimize by saving only the files that need compilation 941 saveProjectBooleanProperty(PROPERTY_COMPILE_AIDL , mAidlToCompile.size() > 0); 942 943 return true; 944 } 945 946 /** 947 * Returns the {@link IFile} handle to the destination file for a given aild source file 948 * ({@link AidlData}). 949 * @param aidlData the data for the aidl source file. 950 * @param createFolders whether or not the parent folder of the destination should be created 951 * if it does not exist. 952 * @param monitor the progress monitor 953 * @return the handle to the destination file. 954 * @throws CoreException 955 */ getGenDestinationFile(AidlData aidlData, boolean createFolders, IProgressMonitor monitor)956 private IFile getGenDestinationFile(AidlData aidlData, boolean createFolders, 957 IProgressMonitor monitor) throws CoreException { 958 // build the destination folder path. 959 // Use the path of the source file, except for the path leading to its source folder, 960 // and for the last segment which is the filename. 961 int segmentToSourceFolderCount = aidlData.sourceFolder.getFullPath().segmentCount(); 962 IPath packagePath = aidlData.aidlFile.getFullPath().removeFirstSegments( 963 segmentToSourceFolderCount).removeLastSegments(1); 964 Path destinationPath = new Path(packagePath.toString()); 965 966 // get an IFolder for this path. It's relative to the 'gen' folder already 967 IFolder destinationFolder = mGenFolder.getFolder(destinationPath); 968 969 // create it if needed. 970 if (destinationFolder.exists() == false && createFolders) { 971 createFolder(destinationFolder, monitor); 972 } 973 974 // Build the Java file name from the aidl name. 975 String javaName = aidlData.aidlFile.getName().replaceAll(AndroidConstants.RE_AIDL_EXT, 976 AndroidConstants.DOT_JAVA); 977 978 // get the resource for the java file. 979 IFile javaFile = destinationFolder.getFile(javaName); 980 return javaFile; 981 } 982 983 /** 984 * Creates the destination folder. Because 985 * {@link IFolder#create(boolean, boolean, IProgressMonitor)} only works if the parent folder 986 * already exists, this goes and ensure that all the parent folders actually exist, or it 987 * creates them as well. 988 * @param destinationFolder The folder to create 989 * @param monitor the {@link IProgressMonitor}, 990 * @throws CoreException 991 */ createFolder(IFolder destinationFolder, IProgressMonitor monitor)992 private void createFolder(IFolder destinationFolder, IProgressMonitor monitor) 993 throws CoreException { 994 995 // check the parent exist and create if necessary. 996 IContainer parent = destinationFolder.getParent(); 997 if (parent.getType() == IResource.FOLDER && parent.exists() == false) { 998 createFolder((IFolder)parent, monitor); 999 } 1000 1001 // create the folder. 1002 destinationFolder.create(true /*force*/, true /*local*/, 1003 new SubProgressMonitor(monitor, 10)); 1004 } 1005 1006 /** 1007 * Execute the aidl command line, parse the output, and mark the aidl file 1008 * with any reported errors. 1009 * @param command the String array containing the command line to execute. 1010 * @param file The IFile object representing the aidl file being 1011 * compiled. 1012 * @return false if the exec failed, and build needs to be aborted. 1013 */ execAidl(String[] command, IFile file)1014 private boolean execAidl(String[] command, IFile file) { 1015 // do the exec 1016 try { 1017 Process p = Runtime.getRuntime().exec(command); 1018 1019 // list to store each line of stderr 1020 ArrayList<String> results = new ArrayList<String>(); 1021 1022 // get the output and return code from the process 1023 int result = grabProcessOutput(p, results); 1024 1025 // attempt to parse the error output 1026 boolean error = parseAidlOutput(results, file); 1027 1028 // If the process failed and we couldn't parse the output 1029 // we pring a message, mark the project and exit 1030 if (result != 0 && error == true) { 1031 // display the message in the console. 1032 AdtPlugin.printErrorToConsole(getProject(), results.toArray()); 1033 1034 // mark the project and exit 1035 markProject(AndroidConstants.MARKER_ADT, Messages.Unparsed_AIDL_Errors, 1036 IMarker.SEVERITY_ERROR); 1037 return false; 1038 } 1039 } catch (IOException e) { 1040 // mark the project and exit 1041 String msg = String.format(Messages.AIDL_Exec_Error, command[0]); 1042 markProject(AndroidConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); 1043 return false; 1044 } catch (InterruptedException e) { 1045 // mark the project and exit 1046 String msg = String.format(Messages.AIDL_Exec_Error, command[0]); 1047 markProject(AndroidConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); 1048 return false; 1049 } 1050 1051 return true; 1052 } 1053 1054 /** 1055 * Goes through the build paths and fills the list of aidl files to compile 1056 * ({@link #mAidlToCompile}). 1057 * @param project The project. 1058 * @param sourceFolderPathList The list of source folder paths. 1059 */ buildAidlCompilationList(IProject project, ArrayList<IPath> sourceFolderPathList)1060 private void buildAidlCompilationList(IProject project, 1061 ArrayList<IPath> sourceFolderPathList) { 1062 IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); 1063 for (IPath sourceFolderPath : sourceFolderPathList) { 1064 IFolder sourceFolder = root.getFolder(sourceFolderPath); 1065 // we don't look in the 'gen' source folder as there will be no source in there. 1066 if (sourceFolder.exists() && sourceFolder.equals(mGenFolder) == false) { 1067 scanFolderForAidl(sourceFolder, sourceFolder); 1068 } 1069 } 1070 } 1071 1072 /** 1073 * Scans a folder and fills the list of aidl files to compile. 1074 * @param sourceFolder the root source folder. 1075 * @param folder The folder to scan. 1076 */ scanFolderForAidl(IFolder sourceFolder, IFolder folder)1077 private void scanFolderForAidl(IFolder sourceFolder, IFolder folder) { 1078 try { 1079 IResource[] members = folder.members(); 1080 for (IResource r : members) { 1081 // get the type of the resource 1082 switch (r.getType()) { 1083 case IResource.FILE: 1084 // if this a file, check that the file actually exist 1085 // and that it's an aidl file 1086 if (r.exists() && 1087 AndroidConstants.EXT_AIDL.equalsIgnoreCase(r.getFileExtension())) { 1088 mAidlToCompile.add(new AidlData(sourceFolder, (IFile)r)); 1089 } 1090 break; 1091 case IResource.FOLDER: 1092 // recursively go through children 1093 scanFolderForAidl(sourceFolder, (IFolder)r); 1094 break; 1095 default: 1096 // this would mean it's a project or the workspace root 1097 // which is unlikely to happen. we do nothing 1098 break; 1099 } 1100 } 1101 } catch (CoreException e) { 1102 // Couldn't get the members list for some reason. Just return. 1103 } 1104 } 1105 1106 1107 /** 1108 * Parse the output of aidl and mark the file with any errors. 1109 * @param lines The output to parse. 1110 * @param file The file to mark with error. 1111 * @return true if the parsing failed, false if success. 1112 */ parseAidlOutput(ArrayList<String> lines, IFile file)1113 private boolean parseAidlOutput(ArrayList<String> lines, IFile file) { 1114 // nothing to parse? just return false; 1115 if (lines.size() == 0) { 1116 return false; 1117 } 1118 1119 Matcher m; 1120 1121 for (int i = 0; i < lines.size(); i++) { 1122 String p = lines.get(i); 1123 1124 m = sAidlPattern1.matcher(p); 1125 if (m.matches()) { 1126 // we can ignore group 1 which is the location since we already 1127 // have a IFile object representing the aidl file. 1128 String lineStr = m.group(2); 1129 String msg = m.group(3); 1130 1131 // get the line number 1132 int line = 0; 1133 try { 1134 line = Integer.parseInt(lineStr); 1135 } catch (NumberFormatException e) { 1136 // looks like the string we extracted wasn't a valid 1137 // file number. Parsing failed and we return true 1138 return true; 1139 } 1140 1141 // mark the file 1142 BaseProjectHelper.markResource(file, AndroidConstants.MARKER_AIDL, msg, line, 1143 IMarker.SEVERITY_ERROR); 1144 1145 // success, go to the next line 1146 continue; 1147 } 1148 1149 // invalid line format, flag as error, and bail 1150 return true; 1151 } 1152 1153 return false; 1154 } 1155 1156 /** 1157 * Merge the current list of aidl file to compile/remove with the new one. 1158 * @param toCompile List of file to compile 1159 * @param toRemove List of file to remove 1160 */ mergeAidlFileModifications(ArrayList<AidlData> toCompile, ArrayList<AidlData> toRemove)1161 private void mergeAidlFileModifications(ArrayList<AidlData> toCompile, 1162 ArrayList<AidlData> toRemove) { 1163 // loop through the new toRemove list, and add it to the old one, 1164 // plus remove any file that was still to compile and that are now 1165 // removed 1166 for (AidlData r : toRemove) { 1167 if (mAidlToRemove.indexOf(r) == -1) { 1168 mAidlToRemove.add(r); 1169 } 1170 1171 int index = mAidlToCompile.indexOf(r); 1172 if (index != -1) { 1173 mAidlToCompile.remove(index); 1174 } 1175 } 1176 1177 // now loop through the new files to compile and add it to the list. 1178 // Also look for them in the remove list, this would mean that they 1179 // were removed, then added back, and we shouldn't remove them, just 1180 // recompile them. 1181 for (AidlData r : toCompile) { 1182 if (mAidlToCompile.indexOf(r) == -1) { 1183 mAidlToCompile.add(r); 1184 } 1185 1186 int index = mAidlToRemove.indexOf(r); 1187 if (index != -1) { 1188 mAidlToRemove.remove(index); 1189 } 1190 } 1191 } 1192 } 1193