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.project; 18 19 import static com.android.ide.eclipse.adt.AdtConstants.COMPILER_COMPLIANCE_PREFERRED; 20 21 import com.android.SdkConstants; 22 import com.android.annotations.NonNull; 23 import com.android.ide.common.xml.ManifestData; 24 import com.android.ide.eclipse.adt.AdtConstants; 25 import com.android.ide.eclipse.adt.AdtPlugin; 26 import com.android.ide.eclipse.adt.internal.build.builders.PostCompilerBuilder; 27 import com.android.ide.eclipse.adt.internal.build.builders.PreCompilerBuilder; 28 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; 29 import com.android.ide.eclipse.adt.internal.sdk.ProjectState; 30 import com.android.ide.eclipse.adt.internal.sdk.Sdk; 31 import com.android.utils.Pair; 32 33 import org.eclipse.core.resources.ICommand; 34 import org.eclipse.core.resources.IFile; 35 import org.eclipse.core.resources.IFolder; 36 import org.eclipse.core.resources.IMarker; 37 import org.eclipse.core.resources.IProject; 38 import org.eclipse.core.resources.IProjectDescription; 39 import org.eclipse.core.resources.IResource; 40 import org.eclipse.core.resources.IWorkspace; 41 import org.eclipse.core.resources.IncrementalProjectBuilder; 42 import org.eclipse.core.resources.ResourcesPlugin; 43 import org.eclipse.core.runtime.CoreException; 44 import org.eclipse.core.runtime.IPath; 45 import org.eclipse.core.runtime.IProgressMonitor; 46 import org.eclipse.core.runtime.NullProgressMonitor; 47 import org.eclipse.core.runtime.Path; 48 import org.eclipse.core.runtime.QualifiedName; 49 import org.eclipse.jdt.core.IClasspathEntry; 50 import org.eclipse.jdt.core.IJavaModel; 51 import org.eclipse.jdt.core.IJavaProject; 52 import org.eclipse.jdt.core.JavaCore; 53 import org.eclipse.jdt.core.JavaModelException; 54 import org.eclipse.jdt.internal.corext.util.JavaModelUtil; 55 import org.eclipse.jdt.launching.IVMInstall; 56 import org.eclipse.jdt.launching.IVMInstall2; 57 import org.eclipse.jdt.launching.IVMInstallType; 58 import org.eclipse.jdt.launching.JavaRuntime; 59 60 import java.util.ArrayList; 61 import java.util.HashMap; 62 import java.util.List; 63 import java.util.Map; 64 import java.util.TreeMap; 65 66 /** 67 * Utility class to manipulate Project parameters/properties. 68 */ 69 public final class ProjectHelper { 70 public final static int COMPILER_COMPLIANCE_OK = 0; 71 public final static int COMPILER_COMPLIANCE_LEVEL = 1; 72 public final static int COMPILER_COMPLIANCE_SOURCE = 2; 73 public final static int COMPILER_COMPLIANCE_CODEGEN_TARGET = 3; 74 75 /** 76 * Adds the given ClasspathEntry object to the class path entries. 77 * This method does not check whether the entry is already defined in the project. 78 * 79 * @param entries The class path entries to read. A copy will be returned. 80 * @param newEntry The new class path entry to add. 81 * @return A new class path entries array. 82 */ 83 public static IClasspathEntry[] addEntryToClasspath( 84 IClasspathEntry[] entries, IClasspathEntry newEntry) { 85 int n = entries.length; 86 IClasspathEntry[] newEntries = new IClasspathEntry[n + 1]; 87 System.arraycopy(entries, 0, newEntries, 0, n); 88 newEntries[n] = newEntry; 89 return newEntries; 90 } 91 92 /** 93 * Replaces the given ClasspathEntry in the classpath entries. 94 * 95 * If the classpath does not yet exists (Check is based on entry path), then it is added. 96 * 97 * @param entries The class path entries to read. The same array (replace) or a copy (add) 98 * will be returned. 99 * @param newEntry The new class path entry to add. 100 * @return The same array (replace) or a copy (add) will be returned. 101 * 102 * @see IClasspathEntry#getPath() 103 */ 104 public static IClasspathEntry[] replaceEntryInClasspath( 105 IClasspathEntry[] entries, IClasspathEntry newEntry) { 106 107 IPath path = newEntry.getPath(); 108 for (int i = 0, count = entries.length; i < count ; i++) { 109 if (path.equals(entries[i].getPath())) { 110 entries[i] = newEntry; 111 return entries; 112 } 113 } 114 115 return addEntryToClasspath(entries, newEntry); 116 } 117 118 /** 119 * Adds the corresponding source folder to the project's class path entries. 120 * This method does not check whether the entry is already defined in the project. 121 * 122 * @param javaProject The java project of which path entries to update. 123 * @param newEntry The new class path entry to add. 124 * @throws JavaModelException 125 */ 126 public static void addEntryToClasspath(IJavaProject javaProject, IClasspathEntry newEntry) 127 throws JavaModelException { 128 129 IClasspathEntry[] entries = javaProject.getRawClasspath(); 130 entries = addEntryToClasspath(entries, newEntry); 131 javaProject.setRawClasspath(entries, new NullProgressMonitor()); 132 } 133 134 /** 135 * Checks whether the given class path entry is already defined in the project. 136 * 137 * @param javaProject The java project of which path entries to check. 138 * @param newEntry The parent source folder to remove. 139 * @return True if the class path entry is already defined. 140 * @throws JavaModelException 141 */ 142 public static boolean isEntryInClasspath(IJavaProject javaProject, IClasspathEntry newEntry) 143 throws JavaModelException { 144 145 IClasspathEntry[] entries = javaProject.getRawClasspath(); 146 for (IClasspathEntry entry : entries) { 147 if (entry.equals(newEntry)) { 148 return true; 149 } 150 } 151 return false; 152 } 153 154 /** 155 * Remove a classpath entry from the array. 156 * @param entries The class path entries to read. A copy will be returned 157 * @param index The index to remove. 158 * @return A new class path entries array. 159 */ 160 public static IClasspathEntry[] removeEntryFromClasspath( 161 IClasspathEntry[] entries, int index) { 162 int n = entries.length; 163 IClasspathEntry[] newEntries = new IClasspathEntry[n-1]; 164 165 // copy the entries before index 166 System.arraycopy(entries, 0, newEntries, 0, index); 167 168 // copy the entries after index 169 System.arraycopy(entries, index + 1, newEntries, index, 170 entries.length - index - 1); 171 172 return newEntries; 173 } 174 175 /** 176 * Converts a OS specific path into a path valid for the java doc location 177 * attributes of a project. 178 * @param javaDocOSLocation The OS specific path. 179 * @return a valid path for the java doc location. 180 */ 181 public static String getJavaDocPath(String javaDocOSLocation) { 182 // first thing we do is convert the \ into / 183 String javaDoc = javaDocOSLocation.replaceAll("\\\\", //$NON-NLS-1$ 184 AdtConstants.WS_SEP); 185 186 // then we add file: at the beginning for unix path, and file:/ for non 187 // unix path 188 if (javaDoc.startsWith(AdtConstants.WS_SEP)) { 189 return "file:" + javaDoc; //$NON-NLS-1$ 190 } 191 192 return "file:/" + javaDoc; //$NON-NLS-1$ 193 } 194 195 /** 196 * Look for a specific classpath entry by full path and return its index. 197 * @param entries The entry array to search in. 198 * @param entryPath The OS specific path of the entry. 199 * @param entryKind The kind of the entry. Accepted values are 0 200 * (no filter), IClasspathEntry.CPE_LIBRARY, IClasspathEntry.CPE_PROJECT, 201 * IClasspathEntry.CPE_SOURCE, IClasspathEntry.CPE_VARIABLE, 202 * and IClasspathEntry.CPE_CONTAINER 203 * @return the index of the found classpath entry or -1. 204 */ 205 public static int findClasspathEntryByPath(IClasspathEntry[] entries, 206 String entryPath, int entryKind) { 207 for (int i = 0 ; i < entries.length ; i++) { 208 IClasspathEntry entry = entries[i]; 209 210 int kind = entry.getEntryKind(); 211 212 if (kind == entryKind || entryKind == 0) { 213 // get the path 214 IPath path = entry.getPath(); 215 216 String osPathString = path.toOSString(); 217 if (osPathString.equals(entryPath)) { 218 return i; 219 } 220 } 221 } 222 223 // not found, return bad index. 224 return -1; 225 } 226 227 /** 228 * Look for a specific classpath entry for file name only and return its 229 * index. 230 * @param entries The entry array to search in. 231 * @param entryName The filename of the entry. 232 * @param entryKind The kind of the entry. Accepted values are 0 233 * (no filter), IClasspathEntry.CPE_LIBRARY, IClasspathEntry.CPE_PROJECT, 234 * IClasspathEntry.CPE_SOURCE, IClasspathEntry.CPE_VARIABLE, 235 * and IClasspathEntry.CPE_CONTAINER 236 * @param startIndex Index where to start the search 237 * @return the index of the found classpath entry or -1. 238 */ 239 public static int findClasspathEntryByName(IClasspathEntry[] entries, 240 String entryName, int entryKind, int startIndex) { 241 if (startIndex < 0) { 242 startIndex = 0; 243 } 244 for (int i = startIndex ; i < entries.length ; i++) { 245 IClasspathEntry entry = entries[i]; 246 247 int kind = entry.getEntryKind(); 248 249 if (kind == entryKind || entryKind == 0) { 250 // get the path 251 IPath path = entry.getPath(); 252 String name = path.segment(path.segmentCount()-1); 253 254 if (name.equals(entryName)) { 255 return i; 256 } 257 } 258 } 259 260 // not found, return bad index. 261 return -1; 262 } 263 264 public static boolean updateProject(IJavaProject project) { 265 return updateProjects(new IJavaProject[] { project}); 266 } 267 268 /** 269 * Update the android-specific projects's classpath containers. 270 * @param projects the projects to update 271 * @return 272 */ 273 public static boolean updateProjects(IJavaProject[] projects) { 274 boolean r = AndroidClasspathContainerInitializer.updateProjects(projects); 275 if (r) { 276 return LibraryClasspathContainerInitializer.updateProjects(projects); 277 } 278 return false; 279 } 280 281 /** 282 * Fix the project. This checks the SDK location. 283 * @param project The project to fix. 284 * @throws JavaModelException 285 */ 286 public static void fixProject(IProject project) throws JavaModelException { 287 if (AdtPlugin.getOsSdkFolder().length() == 0) { 288 AdtPlugin.printToConsole(project, "Unknown SDK Location, project not fixed."); 289 return; 290 } 291 292 // get a java project 293 IJavaProject javaProject = JavaCore.create(project); 294 fixProjectClasspathEntries(javaProject); 295 } 296 297 /** 298 * Fix the project classpath entries. The method ensures that: 299 * <ul> 300 * <li>The project does not reference any old android.zip/android.jar archive.</li> 301 * <li>The project does not use its output folder as a sourc folder.</li> 302 * <li>The project does not reference a desktop JRE</li> 303 * <li>The project references the AndroidClasspathContainer. 304 * </ul> 305 * @param javaProject The project to fix. 306 * @throws JavaModelException 307 */ 308 public static void fixProjectClasspathEntries(IJavaProject javaProject) 309 throws JavaModelException { 310 311 // get the project classpath 312 IClasspathEntry[] entries = javaProject.getRawClasspath(); 313 IClasspathEntry[] oldEntries = entries; 314 boolean forceRewriteOfCPE = false; 315 316 // check if the JRE is set as library 317 int jreIndex = ProjectHelper.findClasspathEntryByPath(entries, JavaRuntime.JRE_CONTAINER, 318 IClasspathEntry.CPE_CONTAINER); 319 if (jreIndex != -1) { 320 // the project has a JRE included, we remove it 321 entries = ProjectHelper.removeEntryFromClasspath(entries, jreIndex); 322 } 323 324 // get the output folder 325 IPath outputFolder = javaProject.getOutputLocation(); 326 327 boolean foundFrameworkContainer = false; 328 IClasspathEntry foundLibrariesContainer = null; 329 IClasspathEntry foundDependenciesContainer = null; 330 331 for (int i = 0 ; i < entries.length ;) { 332 // get the entry and kind 333 IClasspathEntry entry = entries[i]; 334 int kind = entry.getEntryKind(); 335 336 if (kind == IClasspathEntry.CPE_SOURCE) { 337 IPath path = entry.getPath(); 338 339 if (path.equals(outputFolder)) { 340 entries = ProjectHelper.removeEntryFromClasspath(entries, i); 341 342 // continue, to skip the i++; 343 continue; 344 } 345 } else if (kind == IClasspathEntry.CPE_CONTAINER) { 346 String path = entry.getPath().toString(); 347 if (AdtConstants.CONTAINER_FRAMEWORK.equals(path)) { 348 foundFrameworkContainer = true; 349 } else if (AdtConstants.CONTAINER_PRIVATE_LIBRARIES.equals(path)) { 350 foundLibrariesContainer = entry; 351 } else if (AdtConstants.CONTAINER_DEPENDENCIES.equals(path)) { 352 foundDependenciesContainer = entry; 353 } 354 } 355 356 i++; 357 } 358 359 // look to see if we have the m2eclipse nature 360 boolean m2eNature = false; 361 try { 362 m2eNature = javaProject.getProject().hasNature("org.eclipse.m2e.core.maven2Nature"); 363 } catch (CoreException e) { 364 AdtPlugin.log(e, "Failed to query project %s for m2e nature", 365 javaProject.getProject().getName()); 366 } 367 368 369 // if the framework container is not there, we add it 370 if (!foundFrameworkContainer) { 371 // add the android container to the array 372 entries = ProjectHelper.addEntryToClasspath(entries, 373 JavaCore.newContainerEntry(new Path(AdtConstants.CONTAINER_FRAMEWORK))); 374 } 375 376 // same thing for the library container 377 if (foundLibrariesContainer == null) { 378 // add the exported libraries android container to the array 379 entries = ProjectHelper.addEntryToClasspath(entries, 380 JavaCore.newContainerEntry( 381 new Path(AdtConstants.CONTAINER_PRIVATE_LIBRARIES), true)); 382 } else if (!m2eNature && !foundLibrariesContainer.isExported()) { 383 // the container is present but it's not exported and since there's no m2e nature 384 // we do want it to be exported. 385 // keep all the other parameters the same. 386 entries = ProjectHelper.replaceEntryInClasspath(entries, 387 JavaCore.newContainerEntry( 388 new Path(AdtConstants.CONTAINER_PRIVATE_LIBRARIES), 389 foundLibrariesContainer.getAccessRules(), 390 foundLibrariesContainer.getExtraAttributes(), 391 true)); 392 forceRewriteOfCPE = true; 393 } 394 395 // same thing for the dependencies container 396 if (foundDependenciesContainer == null) { 397 // add the android dependencies container to the array 398 entries = ProjectHelper.addEntryToClasspath(entries, 399 JavaCore.newContainerEntry( 400 new Path(AdtConstants.CONTAINER_DEPENDENCIES), true)); 401 } else if (!m2eNature && !foundDependenciesContainer.isExported()) { 402 // the container is present but it's not exported and since there's no m2e nature 403 // we do want it to be exported. 404 // keep all the other parameters the same. 405 entries = ProjectHelper.replaceEntryInClasspath(entries, 406 JavaCore.newContainerEntry( 407 new Path(AdtConstants.CONTAINER_DEPENDENCIES), 408 foundDependenciesContainer.getAccessRules(), 409 foundDependenciesContainer.getExtraAttributes(), 410 true)); 411 forceRewriteOfCPE = true; 412 } 413 414 // set the new list of entries to the project 415 if (entries != oldEntries || forceRewriteOfCPE) { 416 javaProject.setRawClasspath(entries, new NullProgressMonitor()); 417 } 418 419 // If needed, check and fix compiler compliance and source compatibility 420 ProjectHelper.checkAndFixCompilerCompliance(javaProject); 421 } 422 423 424 /** 425 * Checks the project compiler compliance level is supported. 426 * @param javaProject The project to check 427 * @return A pair with the first integer being an error code, and the second value 428 * being the invalid value found or null. The error code can be: <ul> 429 * <li><code>COMPILER_COMPLIANCE_OK</code> if the project is properly configured</li> 430 * <li><code>COMPILER_COMPLIANCE_LEVEL</code> for unsupported compiler level</li> 431 * <li><code>COMPILER_COMPLIANCE_SOURCE</code> for unsupported source compatibility</li> 432 * <li><code>COMPILER_COMPLIANCE_CODEGEN_TARGET</code> for unsupported .class format</li> 433 * </ul> 434 */ 435 public static final Pair<Integer, String> checkCompilerCompliance(IJavaProject javaProject) { 436 // get the project compliance level option 437 String compliance = javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true); 438 439 // check it against a list of valid compliance level strings. 440 if (checkCompliance(compliance) == false) { 441 // if we didn't find the proper compliance level, we return an error 442 return Pair.of(COMPILER_COMPLIANCE_LEVEL, compliance); 443 } 444 445 // otherwise we check source compatibility 446 String source = javaProject.getOption(JavaCore.COMPILER_SOURCE, true); 447 448 // check it against a list of valid compliance level strings. 449 if (checkCompliance(source) == false) { 450 // if we didn't find the proper compliance level, we return an error 451 return Pair.of(COMPILER_COMPLIANCE_SOURCE, source); 452 } 453 454 // otherwise check codegen level 455 String codeGen = javaProject.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true); 456 457 // check it against a list of valid compliance level strings. 458 if (checkCompliance(codeGen) == false) { 459 // if we didn't find the proper compliance level, we return an error 460 return Pair.of(COMPILER_COMPLIANCE_CODEGEN_TARGET, codeGen); 461 } 462 463 return Pair.of(COMPILER_COMPLIANCE_OK, null); 464 } 465 466 /** 467 * Checks the project compiler compliance level is supported. 468 * @param project The project to check 469 * @return A pair with the first integer being an error code, and the second value 470 * being the invalid value found or null. The error code can be: <ul> 471 * <li><code>COMPILER_COMPLIANCE_OK</code> if the project is properly configured</li> 472 * <li><code>COMPILER_COMPLIANCE_LEVEL</code> for unsupported compiler level</li> 473 * <li><code>COMPILER_COMPLIANCE_SOURCE</code> for unsupported source compatibility</li> 474 * <li><code>COMPILER_COMPLIANCE_CODEGEN_TARGET</code> for unsupported .class format</li> 475 * </ul> 476 */ 477 public static final Pair<Integer, String> checkCompilerCompliance(IProject project) { 478 // get the java project from the IProject resource object 479 IJavaProject javaProject = JavaCore.create(project); 480 481 // check and return the result. 482 return checkCompilerCompliance(javaProject); 483 } 484 485 486 /** 487 * Checks, and fixes if needed, the compiler compliance level, and the source compatibility 488 * level 489 * @param project The project to check and fix. 490 */ 491 public static final void checkAndFixCompilerCompliance(IProject project) { 492 // FIXME This method is never used. Shall we just removed it? 493 // {@link #checkAndFixCompilerCompliance(IJavaProject)} is used instead. 494 495 // get the java project from the IProject resource object 496 IJavaProject javaProject = JavaCore.create(project); 497 498 // Now we check the compiler compliance level and make sure it is valid 499 checkAndFixCompilerCompliance(javaProject); 500 } 501 502 /** 503 * Checks, and fixes if needed, the compiler compliance level, and the source compatibility 504 * level 505 * @param javaProject The Java project to check and fix. 506 */ 507 public static final void checkAndFixCompilerCompliance(IJavaProject javaProject) { 508 Pair<Integer, String> result = checkCompilerCompliance(javaProject); 509 if (result.getFirst().intValue() != COMPILER_COMPLIANCE_OK) { 510 // setup the preferred compiler compliance level. 511 javaProject.setOption(JavaCore.COMPILER_COMPLIANCE, 512 AdtConstants.COMPILER_COMPLIANCE_PREFERRED); 513 javaProject.setOption(JavaCore.COMPILER_SOURCE, 514 AdtConstants.COMPILER_COMPLIANCE_PREFERRED); 515 javaProject.setOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, 516 AdtConstants.COMPILER_COMPLIANCE_PREFERRED); 517 518 // clean the project to make sure we recompile 519 try { 520 javaProject.getProject().build(IncrementalProjectBuilder.CLEAN_BUILD, 521 new NullProgressMonitor()); 522 } catch (CoreException e) { 523 AdtPlugin.printErrorToConsole(javaProject.getProject(), 524 "Project compiler settings changed. Clean your project."); 525 } 526 } 527 } 528 529 /** 530 * Makes the given project use JDK 6 (or more specifically, 531 * {@link AdtConstants#COMPILER_COMPLIANCE_PREFERRED} as the compilation 532 * target, regardless of what the default IDE JDK level is, provided a JRE 533 * of the given level is installed. 534 * 535 * @param javaProject the Java project 536 * @throws CoreException if the IDE throws an exception setting the compiler 537 * level 538 */ 539 @SuppressWarnings("restriction") // JDT API for setting compliance options 540 public static void enforcePreferredCompilerCompliance(@NonNull IJavaProject javaProject) 541 throws CoreException { 542 String compliance = javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true); 543 if (compliance == null || 544 JavaModelUtil.isVersionLessThan(compliance, COMPILER_COMPLIANCE_PREFERRED)) { 545 IVMInstallType[] types = JavaRuntime.getVMInstallTypes(); 546 for (int i = 0; i < types.length; i++) { 547 IVMInstallType type = types[i]; 548 IVMInstall[] installs = type.getVMInstalls(); 549 for (int j = 0; j < installs.length; j++) { 550 IVMInstall install = installs[j]; 551 if (install instanceof IVMInstall2) { 552 IVMInstall2 install2 = (IVMInstall2) install; 553 // Java version can be 1.6.0, and preferred is 1.6 554 if (install2.getJavaVersion().startsWith(COMPILER_COMPLIANCE_PREFERRED)) { 555 Map<String, String> options = javaProject.getOptions(false); 556 JavaCore.setComplianceOptions(COMPILER_COMPLIANCE_PREFERRED, options); 557 JavaModelUtil.setDefaultClassfileOptions(options, 558 COMPILER_COMPLIANCE_PREFERRED); 559 javaProject.setOptions(options); 560 return; 561 } 562 } 563 } 564 } 565 } 566 } 567 568 /** 569 * Returns a {@link IProject} by its running application name, as it returned by the AVD. 570 * <p/> 571 * <var>applicationName</var> will in most case be the package declared in the manifest, but 572 * can, in some cases, be a custom process name declared in the manifest, in the 573 * <code>application</code>, <code>activity</code>, <code>receiver</code>, or 574 * <code>service</code> nodes. 575 * @param applicationName The application name. 576 * @return a project or <code>null</code> if no matching project were found. 577 */ 578 public static IProject findAndroidProjectByAppName(String applicationName) { 579 // Get the list of project for the current workspace 580 IWorkspace workspace = ResourcesPlugin.getWorkspace(); 581 IProject[] projects = workspace.getRoot().getProjects(); 582 583 // look for a project that matches the packageName of the app 584 // we're trying to debug 585 for (IProject p : projects) { 586 if (p.isOpen()) { 587 try { 588 if (p.hasNature(AdtConstants.NATURE_DEFAULT) == false) { 589 // ignore non android projects 590 continue; 591 } 592 } catch (CoreException e) { 593 // failed to get the nature? skip project. 594 continue; 595 } 596 597 // check that there is indeed a manifest file. 598 IFile manifestFile = getManifest(p); 599 if (manifestFile == null) { 600 // no file? skip this project. 601 continue; 602 } 603 604 ManifestData data = AndroidManifestHelper.parseForData(manifestFile); 605 if (data == null) { 606 // skip this project. 607 continue; 608 } 609 610 String manifestPackage = data.getPackage(); 611 612 if (manifestPackage != null && manifestPackage.equals(applicationName)) { 613 // this is the project we were looking for! 614 return p; 615 } else { 616 // if the package and application name don't match, 617 // we look for other possible process names declared in the manifest. 618 String[] processes = data.getProcesses(); 619 for (String process : processes) { 620 if (process.equals(applicationName)) { 621 return p; 622 } 623 } 624 } 625 } 626 } 627 628 return null; 629 630 } 631 632 public static void fixProjectNatureOrder(IProject project) throws CoreException { 633 IProjectDescription description = project.getDescription(); 634 String[] natures = description.getNatureIds(); 635 636 // if the android nature is not the first one, we reorder them 637 if (AdtConstants.NATURE_DEFAULT.equals(natures[0]) == false) { 638 // look for the index 639 for (int i = 0 ; i < natures.length ; i++) { 640 if (AdtConstants.NATURE_DEFAULT.equals(natures[i])) { 641 // if we try to just reorder the array in one pass, this doesn't do 642 // anything. I guess JDT check that we are actually adding/removing nature. 643 // So, first we'll remove the android nature, and then add it back. 644 645 // remove the android nature 646 removeNature(project, AdtConstants.NATURE_DEFAULT); 647 648 // now add it back at the first index. 649 description = project.getDescription(); 650 natures = description.getNatureIds(); 651 652 String[] newNatures = new String[natures.length + 1]; 653 654 // first one is android 655 newNatures[0] = AdtConstants.NATURE_DEFAULT; 656 657 // next the rest that was before the android nature 658 System.arraycopy(natures, 0, newNatures, 1, natures.length); 659 660 // set the new natures 661 description.setNatureIds(newNatures); 662 project.setDescription(description, null); 663 664 // and stop 665 break; 666 } 667 } 668 } 669 } 670 671 672 /** 673 * Removes a specific nature from a project. 674 * @param project The project to remove the nature from. 675 * @param nature The nature id to remove. 676 * @throws CoreException 677 */ 678 public static void removeNature(IProject project, String nature) throws CoreException { 679 IProjectDescription description = project.getDescription(); 680 String[] natures = description.getNatureIds(); 681 682 // check if the project already has the android nature. 683 for (int i = 0; i < natures.length; ++i) { 684 if (nature.equals(natures[i])) { 685 String[] newNatures = new String[natures.length - 1]; 686 if (i > 0) { 687 System.arraycopy(natures, 0, newNatures, 0, i); 688 } 689 System.arraycopy(natures, i + 1, newNatures, i, natures.length - i - 1); 690 description.setNatureIds(newNatures); 691 project.setDescription(description, null); 692 693 return; 694 } 695 } 696 697 } 698 699 /** 700 * Returns if the project has error level markers. 701 * @param includeReferencedProjects flag to also test the referenced projects. 702 * @throws CoreException 703 */ 704 public static boolean hasError(IProject project, boolean includeReferencedProjects) 705 throws CoreException { 706 IMarker[] markers = project.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE); 707 if (markers != null && markers.length > 0) { 708 // the project has marker(s). even though they are "problem" we 709 // don't know their severity. so we loop on them and figure if they 710 // are warnings or errors 711 for (IMarker m : markers) { 712 int s = m.getAttribute(IMarker.SEVERITY, -1); 713 if (s == IMarker.SEVERITY_ERROR) { 714 return true; 715 } 716 } 717 } 718 719 // test the referenced projects if needed. 720 if (includeReferencedProjects) { 721 List<IProject> projects = getReferencedProjects(project); 722 723 for (IProject p : projects) { 724 if (hasError(p, false)) { 725 return true; 726 } 727 } 728 } 729 730 return false; 731 } 732 733 /** 734 * Saves a String property into the persistent storage of a resource. 735 * @param resource The resource into which the string value is saved. 736 * @param propertyName the name of the property. The id of the plug-in is added to this string. 737 * @param value the value to save 738 * @return true if the save succeeded. 739 */ 740 public static boolean saveStringProperty(IResource resource, String propertyName, 741 String value) { 742 QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID, propertyName); 743 744 try { 745 resource.setPersistentProperty(qname, value); 746 } catch (CoreException e) { 747 return false; 748 } 749 750 return true; 751 } 752 753 /** 754 * Loads a String property from the persistent storage of a resource. 755 * @param resource The resource from which the string value is loaded. 756 * @param propertyName the name of the property. The id of the plug-in is added to this string. 757 * @return the property value or null if it was not found. 758 */ 759 public static String loadStringProperty(IResource resource, String propertyName) { 760 QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID, propertyName); 761 762 try { 763 String value = resource.getPersistentProperty(qname); 764 return value; 765 } catch (CoreException e) { 766 return null; 767 } 768 } 769 770 /** 771 * Saves a property into the persistent storage of a resource. 772 * @param resource The resource into which the boolean value is saved. 773 * @param propertyName the name of the property. The id of the plug-in is added to this string. 774 * @param value the value to save 775 * @return true if the save succeeded. 776 */ 777 public static boolean saveBooleanProperty(IResource resource, String propertyName, 778 boolean value) { 779 return saveStringProperty(resource, propertyName, Boolean.toString(value)); 780 } 781 782 /** 783 * Loads a boolean property from the persistent storage of a resource. 784 * @param resource The resource from which the boolean value is loaded. 785 * @param propertyName the name of the property. The id of the plug-in is added to this string. 786 * @param defaultValue The default value to return if the property was not found. 787 * @return the property value or the default value if the property was not found. 788 */ 789 public static boolean loadBooleanProperty(IResource resource, String propertyName, 790 boolean defaultValue) { 791 String value = loadStringProperty(resource, propertyName); 792 if (value != null) { 793 return Boolean.parseBoolean(value); 794 } 795 796 return defaultValue; 797 } 798 799 public static Boolean loadBooleanProperty(IResource resource, String propertyName) { 800 String value = loadStringProperty(resource, propertyName); 801 if (value != null) { 802 return Boolean.valueOf(value); 803 } 804 805 return null; 806 } 807 808 /** 809 * Saves the path of a resource into the persistent storage of a resource. 810 * @param resource The resource into which the resource path is saved. 811 * @param propertyName the name of the property. The id of the plug-in is added to this string. 812 * @param value The resource to save. It's its path that is actually stored. If null, an 813 * empty string is stored. 814 * @return true if the save succeeded 815 */ 816 public static boolean saveResourceProperty(IResource resource, String propertyName, 817 IResource value) { 818 if (value != null) { 819 IPath iPath = value.getFullPath(); 820 return saveStringProperty(resource, propertyName, iPath.toString()); 821 } 822 823 return saveStringProperty(resource, propertyName, ""); //$NON-NLS-1$ 824 } 825 826 /** 827 * Loads the path of a resource from the persistent storage of a resource, and returns the 828 * corresponding IResource object. 829 * @param resource The resource from which the resource path is loaded. 830 * @param propertyName the name of the property. The id of the plug-in is added to this string. 831 * @return The corresponding IResource object (or children interface) or null 832 */ 833 public static IResource loadResourceProperty(IResource resource, String propertyName) { 834 String value = loadStringProperty(resource, propertyName); 835 836 if (value != null && value.length() > 0) { 837 return ResourcesPlugin.getWorkspace().getRoot().findMember(new Path(value)); 838 } 839 840 return null; 841 } 842 843 /** 844 * Returns the list of referenced project that are opened and Java projects. 845 * @param project 846 * @return a new list object containing the opened referenced java project. 847 * @throws CoreException 848 */ 849 public static List<IProject> getReferencedProjects(IProject project) throws CoreException { 850 IProject[] projects = project.getReferencedProjects(); 851 852 ArrayList<IProject> list = new ArrayList<IProject>(); 853 854 for (IProject p : projects) { 855 if (p.isOpen() && p.hasNature(JavaCore.NATURE_ID)) { 856 list.add(p); 857 } 858 } 859 860 return list; 861 } 862 863 864 /** 865 * Checks a Java project compiler level option against a list of supported versions. 866 * @param optionValue the Compiler level option. 867 * @return true if the option value is supproted. 868 */ 869 private static boolean checkCompliance(String optionValue) { 870 for (String s : AdtConstants.COMPILER_COMPLIANCE) { 871 if (s != null && s.equals(optionValue)) { 872 return true; 873 } 874 } 875 876 return false; 877 } 878 879 /** 880 * Returns the apk filename for the given project 881 * @param project The project. 882 * @param config An optional config name. Can be null. 883 */ 884 public static String getApkFilename(IProject project, String config) { 885 if (config != null) { 886 return project.getName() + "-" + config + SdkConstants.DOT_ANDROID_PACKAGE; //$NON-NLS-1$ 887 } 888 889 return project.getName() + SdkConstants.DOT_ANDROID_PACKAGE; 890 } 891 892 /** 893 * Find the list of projects on which this JavaProject is dependent on at the compilation level. 894 * 895 * @param javaProject Java project that we are looking for the dependencies. 896 * @return A list of Java projects for which javaProject depend on. 897 * @throws JavaModelException 898 */ 899 public static List<IJavaProject> getAndroidProjectDependencies(IJavaProject javaProject) 900 throws JavaModelException { 901 String[] requiredProjectNames = javaProject.getRequiredProjectNames(); 902 903 // Go from java project name to JavaProject name 904 IJavaModel javaModel = javaProject.getJavaModel(); 905 906 // loop through all dependent projects and keep only those that are Android projects 907 List<IJavaProject> projectList = new ArrayList<IJavaProject>(requiredProjectNames.length); 908 for (String javaProjectName : requiredProjectNames) { 909 IJavaProject androidJavaProject = javaModel.getJavaProject(javaProjectName); 910 911 //Verify that the project has also the Android Nature 912 try { 913 if (!androidJavaProject.getProject().hasNature(AdtConstants.NATURE_DEFAULT)) { 914 continue; 915 } 916 } catch (CoreException e) { 917 continue; 918 } 919 920 projectList.add(androidJavaProject); 921 } 922 923 return projectList; 924 } 925 926 /** 927 * Returns the android package file as an IFile object for the specified 928 * project. 929 * @param project The project 930 * @return The android package as an IFile object or null if not found. 931 */ 932 public static IFile getApplicationPackage(IProject project) { 933 // get the output folder 934 IFolder outputLocation = BaseProjectHelper.getAndroidOutputFolder(project); 935 936 if (outputLocation == null) { 937 AdtPlugin.printErrorToConsole(project, 938 "Failed to get the output location of the project. Check build path properties" 939 ); 940 return null; 941 } 942 943 944 // get the package path 945 String packageName = project.getName() + SdkConstants.DOT_ANDROID_PACKAGE; 946 IResource r = outputLocation.findMember(packageName); 947 948 // check the package is present 949 if (r instanceof IFile && r.exists()) { 950 return (IFile)r; 951 } 952 953 String msg = String.format("Could not find %1$s!", packageName); 954 AdtPlugin.printErrorToConsole(project, msg); 955 956 return null; 957 } 958 959 /** 960 * Returns an {@link IFile} object representing the manifest for the given project. 961 * 962 * @param project The project containing the manifest file. 963 * @return An IFile object pointing to the manifest or null if the manifest 964 * is missing. 965 */ 966 public static IFile getManifest(IProject project) { 967 IResource r = project.findMember(AdtConstants.WS_SEP 968 + SdkConstants.FN_ANDROID_MANIFEST_XML); 969 970 if (r == null || r.exists() == false || (r instanceof IFile) == false) { 971 return null; 972 } 973 return (IFile) r; 974 } 975 976 /** 977 * Does a full release build of the application, including the libraries. Do not build the 978 * package. 979 * 980 * @param project The project to be built. 981 * @param monitor A eclipse runtime progress monitor to be updated by the builders. 982 * @throws CoreException 983 */ 984 @SuppressWarnings("unchecked") 985 public static void compileInReleaseMode(IProject project, IProgressMonitor monitor) 986 throws CoreException { 987 compileInReleaseMode(project, true /*includeDependencies*/, monitor); 988 } 989 990 /** 991 * Does a full release build of the application, including the libraries. Do not build the 992 * package. 993 * 994 * @param project The project to be built. 995 * @param monitor A eclipse runtime progress monitor to be updated by the builders. 996 * @throws CoreException 997 */ 998 @SuppressWarnings("unchecked") 999 private static void compileInReleaseMode(IProject project, boolean includeDependencies, 1000 IProgressMonitor monitor) 1001 throws CoreException { 1002 1003 if (includeDependencies) { 1004 ProjectState projectState = Sdk.getProjectState(project); 1005 1006 // this gives us all the library projects, direct and indirect dependencies, 1007 // so no need to run this method recursively. 1008 List<IProject> libraries = projectState.getFullLibraryProjects(); 1009 1010 // build dependencies in reverse order to prevent libraries being rebuilt 1011 // due to refresh of other libraries (they would be compiled in the wrong mode). 1012 for (int i = libraries.size() - 1 ; i >= 0 ; i--) { 1013 IProject lib = libraries.get(i); 1014 compileInReleaseMode(lib, false /*includeDependencies*/, monitor); 1015 1016 // force refresh of the dependency. 1017 lib.refreshLocal(IResource.DEPTH_INFINITE, monitor); 1018 } 1019 } 1020 1021 // do a full build on all the builders to guarantee that the builders are called. 1022 // (Eclipse does an optimization where builders are not called if there aren't any 1023 // deltas). 1024 1025 ICommand[] commands = project.getDescription().getBuildSpec(); 1026 for (ICommand command : commands) { 1027 String name = command.getBuilderName(); 1028 if (PreCompilerBuilder.ID.equals(name)) { 1029 Map newArgs = new HashMap(); 1030 newArgs.put(PreCompilerBuilder.RELEASE_REQUESTED, ""); 1031 if (command.getArguments() != null) { 1032 newArgs.putAll(command.getArguments()); 1033 } 1034 1035 project.build(IncrementalProjectBuilder.FULL_BUILD, 1036 PreCompilerBuilder.ID, newArgs, monitor); 1037 } else if (PostCompilerBuilder.ID.equals(name)) { 1038 if (includeDependencies == false) { 1039 // this is a library, we need to build it! 1040 project.build(IncrementalProjectBuilder.FULL_BUILD, name, 1041 command.getArguments(), monitor); 1042 } 1043 } else { 1044 1045 project.build(IncrementalProjectBuilder.FULL_BUILD, name, 1046 command.getArguments(), monitor); 1047 } 1048 } 1049 } 1050 1051 /** 1052 * Force building the project and all its dependencies. 1053 * 1054 * @param project the project to build 1055 * @param kind the build kind 1056 * @param monitor 1057 * @throws CoreException 1058 */ 1059 public static void buildWithDeps(IProject project, int kind, IProgressMonitor monitor) 1060 throws CoreException { 1061 // Get list of projects that we depend on 1062 ProjectState projectState = Sdk.getProjectState(project); 1063 1064 // this gives us all the library projects, direct and indirect dependencies, 1065 // so no need to run this method recursively. 1066 List<IProject> libraries = projectState.getFullLibraryProjects(); 1067 1068 // build dependencies in reverse order to prevent libraries being rebuilt 1069 // due to refresh of other libraries (they would be compiled in the wrong mode). 1070 for (int i = libraries.size() - 1 ; i >= 0 ; i--) { 1071 IProject lib = libraries.get(i); 1072 lib.build(kind, monitor); 1073 lib.refreshLocal(IResource.DEPTH_INFINITE, monitor); 1074 } 1075 1076 project.build(kind, monitor); 1077 } 1078 1079 1080 /** 1081 * Build project incrementally, including making the final packaging even if it is disabled 1082 * by default. 1083 * 1084 * @param project The project to be built. 1085 * @param monitor A eclipse runtime progress monitor to be updated by the builders. 1086 * @throws CoreException 1087 */ 1088 public static void doFullIncrementalDebugBuild(IProject project, IProgressMonitor monitor) 1089 throws CoreException { 1090 // Get list of projects that we depend on 1091 List<IJavaProject> androidProjectList = new ArrayList<IJavaProject>(); 1092 try { 1093 androidProjectList = getAndroidProjectDependencies( 1094 BaseProjectHelper.getJavaProject(project)); 1095 } catch (JavaModelException e) { 1096 AdtPlugin.printErrorToConsole(project, e); 1097 } 1098 // Recursively build dependencies 1099 for (IJavaProject dependency : androidProjectList) { 1100 doFullIncrementalDebugBuild(dependency.getProject(), monitor); 1101 } 1102 1103 // Do an incremental build to pick up all the deltas 1104 project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor); 1105 1106 // If the preferences indicate not to use post compiler optimization 1107 // then the incremental build will have done everything necessary, otherwise, 1108 // we have to run the final builder manually (if requested). 1109 if (AdtPrefs.getPrefs().getBuildSkipPostCompileOnFileSave()) { 1110 // Create the map to pass to the PostC builder 1111 Map<String, String> args = new TreeMap<String, String>(); 1112 args.put(PostCompilerBuilder.POST_C_REQUESTED, ""); //$NON-NLS-1$ 1113 1114 // call the post compiler manually, forcing FULL_BUILD otherwise Eclipse won't 1115 // call the builder since the delta is empty. 1116 project.build(IncrementalProjectBuilder.FULL_BUILD, 1117 PostCompilerBuilder.ID, args, monitor); 1118 } 1119 1120 // because the post compiler builder does a delayed refresh due to 1121 // library not picking the refresh up if it's done during the build, 1122 // we want to force a refresh here as this call is generally asking for 1123 // a build to use the apk right after the call. 1124 project.refreshLocal(IResource.DEPTH_INFINITE, monitor); 1125 } 1126 } 1127