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