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