blob: a32b4ca8b068e85c70a255b2376977a8a8e63ddc [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.internal.project;
import static com.android.ide.eclipse.adt.AdtConstants.COMPILER_COMPLIANCE_PREFERRED;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.ide.common.xml.ManifestData;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.build.builders.PostCompilerBuilder;
import com.android.ide.eclipse.adt.internal.build.builders.PreCompilerBuilder;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.sdklib.BuildToolInfo;
import com.android.sdklib.IAndroidTarget;
import com.android.utils.Pair;
import org.eclipse.core.resources.ICommand;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaModel;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.launching.IVMInstall;
import org.eclipse.jdt.launching.IVMInstall2;
import org.eclipse.jdt.launching.IVMInstallType;
import org.eclipse.jdt.launching.JavaRuntime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/**
* Utility class to manipulate Project parameters/properties.
*/
public final class ProjectHelper {
public final static int COMPILER_COMPLIANCE_OK = 0;
public final static int COMPILER_COMPLIANCE_LEVEL = 1;
public final static int COMPILER_COMPLIANCE_SOURCE = 2;
public final static int COMPILER_COMPLIANCE_CODEGEN_TARGET = 3;
/**
* Adds the given ClasspathEntry object to the class path entries.
* This method does not check whether the entry is already defined in the project.
*
* @param entries The class path entries to read. A copy will be returned.
* @param newEntry The new class path entry to add.
* @return A new class path entries array.
*/
public static IClasspathEntry[] addEntryToClasspath(
IClasspathEntry[] entries, IClasspathEntry newEntry) {
int n = entries.length;
IClasspathEntry[] newEntries = new IClasspathEntry[n + 1];
System.arraycopy(entries, 0, newEntries, 0, n);
newEntries[n] = newEntry;
return newEntries;
}
/**
* Replaces the given ClasspathEntry in the classpath entries.
*
* If the classpath does not yet exists (Check is based on entry path), then it is added.
*
* @param entries The class path entries to read. The same array (replace) or a copy (add)
* will be returned.
* @param newEntry The new class path entry to add.
* @return The same array (replace) or a copy (add) will be returned.
*
* @see IClasspathEntry#getPath()
*/
public static IClasspathEntry[] replaceEntryInClasspath(
IClasspathEntry[] entries, IClasspathEntry newEntry) {
IPath path = newEntry.getPath();
for (int i = 0, count = entries.length; i < count ; i++) {
if (path.equals(entries[i].getPath())) {
entries[i] = newEntry;
return entries;
}
}
return addEntryToClasspath(entries, newEntry);
}
/**
* Adds the corresponding source folder to the project's class path entries.
* This method does not check whether the entry is already defined in the project.
*
* @param javaProject The java project of which path entries to update.
* @param newEntry The new class path entry to add.
* @throws JavaModelException
*/
public static void addEntryToClasspath(IJavaProject javaProject, IClasspathEntry newEntry)
throws JavaModelException {
IClasspathEntry[] entries = javaProject.getRawClasspath();
entries = addEntryToClasspath(entries, newEntry);
javaProject.setRawClasspath(entries, new NullProgressMonitor());
}
/**
* Checks whether the given class path entry is already defined in the project.
*
* @param javaProject The java project of which path entries to check.
* @param newEntry The parent source folder to remove.
* @return True if the class path entry is already defined.
* @throws JavaModelException
*/
public static boolean isEntryInClasspath(IJavaProject javaProject, IClasspathEntry newEntry)
throws JavaModelException {
IClasspathEntry[] entries = javaProject.getRawClasspath();
for (IClasspathEntry entry : entries) {
if (entry.equals(newEntry)) {
return true;
}
}
return false;
}
/**
* Remove a classpath entry from the array.
* @param entries The class path entries to read. A copy will be returned
* @param index The index to remove.
* @return A new class path entries array.
*/
public static IClasspathEntry[] removeEntryFromClasspath(
IClasspathEntry[] entries, int index) {
int n = entries.length;
IClasspathEntry[] newEntries = new IClasspathEntry[n-1];
// copy the entries before index
System.arraycopy(entries, 0, newEntries, 0, index);
// copy the entries after index
System.arraycopy(entries, index + 1, newEntries, index,
entries.length - index - 1);
return newEntries;
}
/**
* Converts a OS specific path into a path valid for the java doc location
* attributes of a project.
* @param javaDocOSLocation The OS specific path.
* @return a valid path for the java doc location.
*/
public static String getJavaDocPath(String javaDocOSLocation) {
// first thing we do is convert the \ into /
String javaDoc = javaDocOSLocation.replaceAll("\\\\", //$NON-NLS-1$
AdtConstants.WS_SEP);
// then we add file: at the beginning for unix path, and file:/ for non
// unix path
if (javaDoc.startsWith(AdtConstants.WS_SEP)) {
return "file:" + javaDoc; //$NON-NLS-1$
}
return "file:/" + javaDoc; //$NON-NLS-1$
}
/**
* Look for a specific classpath entry by full path and return its index.
* @param entries The entry array to search in.
* @param entryPath The OS specific path of the entry.
* @param entryKind The kind of the entry. Accepted values are 0
* (no filter), IClasspathEntry.CPE_LIBRARY, IClasspathEntry.CPE_PROJECT,
* IClasspathEntry.CPE_SOURCE, IClasspathEntry.CPE_VARIABLE,
* and IClasspathEntry.CPE_CONTAINER
* @return the index of the found classpath entry or -1.
*/
public static int findClasspathEntryByPath(IClasspathEntry[] entries,
String entryPath, int entryKind) {
for (int i = 0 ; i < entries.length ; i++) {
IClasspathEntry entry = entries[i];
int kind = entry.getEntryKind();
if (kind == entryKind || entryKind == 0) {
// get the path
IPath path = entry.getPath();
String osPathString = path.toOSString();
if (osPathString.equals(entryPath)) {
return i;
}
}
}
// not found, return bad index.
return -1;
}
/**
* Look for a specific classpath entry for file name only and return its
* index.
* @param entries The entry array to search in.
* @param entryName The filename of the entry.
* @param entryKind The kind of the entry. Accepted values are 0
* (no filter), IClasspathEntry.CPE_LIBRARY, IClasspathEntry.CPE_PROJECT,
* IClasspathEntry.CPE_SOURCE, IClasspathEntry.CPE_VARIABLE,
* and IClasspathEntry.CPE_CONTAINER
* @param startIndex Index where to start the search
* @return the index of the found classpath entry or -1.
*/
public static int findClasspathEntryByName(IClasspathEntry[] entries,
String entryName, int entryKind, int startIndex) {
if (startIndex < 0) {
startIndex = 0;
}
for (int i = startIndex ; i < entries.length ; i++) {
IClasspathEntry entry = entries[i];
int kind = entry.getEntryKind();
if (kind == entryKind || entryKind == 0) {
// get the path
IPath path = entry.getPath();
String name = path.segment(path.segmentCount()-1);
if (name.equals(entryName)) {
return i;
}
}
}
// not found, return bad index.
return -1;
}
public static boolean updateProject(IJavaProject project) {
return updateProjects(new IJavaProject[] { project});
}
/**
* Update the android-specific projects's classpath containers.
* @param projects the projects to update
* @return
*/
public static boolean updateProjects(IJavaProject[] projects) {
boolean r = AndroidClasspathContainerInitializer.updateProjects(projects);
if (r) {
return LibraryClasspathContainerInitializer.updateProjects(projects);
}
return false;
}
/**
* Fix the project. This checks the SDK location.
* @param project The project to fix.
* @throws JavaModelException
*/
public static void fixProject(IProject project) throws JavaModelException {
if (AdtPlugin.getOsSdkFolder().length() == 0) {
AdtPlugin.printToConsole(project, "Unknown SDK Location, project not fixed.");
return;
}
// get a java project
IJavaProject javaProject = JavaCore.create(project);
fixProjectClasspathEntries(javaProject);
}
/**
* Fix the project classpath entries. The method ensures that:
* <ul>
* <li>The project does not reference any old android.zip/android.jar archive.</li>
* <li>The project does not use its output folder as a sourc folder.</li>
* <li>The project does not reference a desktop JRE</li>
* <li>The project references the AndroidClasspathContainer.
* </ul>
* @param javaProject The project to fix.
* @throws JavaModelException
*/
public static void fixProjectClasspathEntries(IJavaProject javaProject)
throws JavaModelException {
// get the project classpath
IClasspathEntry[] entries = javaProject.getRawClasspath();
IClasspathEntry[] oldEntries = entries;
boolean forceRewriteOfCPE = false;
// check if the JRE is set as library
int jreIndex = ProjectHelper.findClasspathEntryByPath(entries, JavaRuntime.JRE_CONTAINER,
IClasspathEntry.CPE_CONTAINER);
if (jreIndex != -1) {
// the project has a JRE included, we remove it
entries = ProjectHelper.removeEntryFromClasspath(entries, jreIndex);
}
// get the output folder
IPath outputFolder = javaProject.getOutputLocation();
boolean foundFrameworkContainer = false;
IClasspathEntry foundLibrariesContainer = null;
IClasspathEntry foundDependenciesContainer = null;
for (int i = 0 ; i < entries.length ;) {
// get the entry and kind
IClasspathEntry entry = entries[i];
int kind = entry.getEntryKind();
if (kind == IClasspathEntry.CPE_SOURCE) {
IPath path = entry.getPath();
if (path.equals(outputFolder)) {
entries = ProjectHelper.removeEntryFromClasspath(entries, i);
// continue, to skip the i++;
continue;
}
} else if (kind == IClasspathEntry.CPE_CONTAINER) {
String path = entry.getPath().toString();
if (AdtConstants.CONTAINER_FRAMEWORK.equals(path)) {
foundFrameworkContainer = true;
} else if (AdtConstants.CONTAINER_PRIVATE_LIBRARIES.equals(path)) {
foundLibrariesContainer = entry;
} else if (AdtConstants.CONTAINER_DEPENDENCIES.equals(path)) {
foundDependenciesContainer = entry;
}
}
i++;
}
// look to see if we have the m2eclipse nature
boolean m2eNature = false;
try {
m2eNature = javaProject.getProject().hasNature("org.eclipse.m2e.core.maven2Nature");
} catch (CoreException e) {
AdtPlugin.log(e, "Failed to query project %s for m2e nature",
javaProject.getProject().getName());
}
// if the framework container is not there, we add it
if (!foundFrameworkContainer) {
// add the android container to the array
entries = ProjectHelper.addEntryToClasspath(entries,
JavaCore.newContainerEntry(new Path(AdtConstants.CONTAINER_FRAMEWORK)));
}
// same thing for the library container
if (foundLibrariesContainer == null) {
// add the exported libraries android container to the array
entries = ProjectHelper.addEntryToClasspath(entries,
JavaCore.newContainerEntry(
new Path(AdtConstants.CONTAINER_PRIVATE_LIBRARIES), true));
} else if (!m2eNature && !foundLibrariesContainer.isExported()) {
// the container is present but it's not exported and since there's no m2e nature
// we do want it to be exported.
// keep all the other parameters the same.
entries = ProjectHelper.replaceEntryInClasspath(entries,
JavaCore.newContainerEntry(
new Path(AdtConstants.CONTAINER_PRIVATE_LIBRARIES),
foundLibrariesContainer.getAccessRules(),
foundLibrariesContainer.getExtraAttributes(),
true));
forceRewriteOfCPE = true;
}
// same thing for the dependencies container
if (foundDependenciesContainer == null) {
// add the android dependencies container to the array
entries = ProjectHelper.addEntryToClasspath(entries,
JavaCore.newContainerEntry(
new Path(AdtConstants.CONTAINER_DEPENDENCIES), true));
} else if (!m2eNature && !foundDependenciesContainer.isExported()) {
// the container is present but it's not exported and since there's no m2e nature
// we do want it to be exported.
// keep all the other parameters the same.
entries = ProjectHelper.replaceEntryInClasspath(entries,
JavaCore.newContainerEntry(
new Path(AdtConstants.CONTAINER_DEPENDENCIES),
foundDependenciesContainer.getAccessRules(),
foundDependenciesContainer.getExtraAttributes(),
true));
forceRewriteOfCPE = true;
}
// set the new list of entries to the project
if (entries != oldEntries || forceRewriteOfCPE) {
javaProject.setRawClasspath(entries, new NullProgressMonitor());
}
// If needed, check and fix compiler compliance and source compatibility
ProjectHelper.checkAndFixCompilerCompliance(javaProject);
}
/**
* Checks the project compiler compliance level is supported.
* @param javaProject The project to check
* @return A pair with the first integer being an error code, and the second value
* being the invalid value found or null. The error code can be: <ul>
* <li><code>COMPILER_COMPLIANCE_OK</code> if the project is properly configured</li>
* <li><code>COMPILER_COMPLIANCE_LEVEL</code> for unsupported compiler level</li>
* <li><code>COMPILER_COMPLIANCE_SOURCE</code> for unsupported source compatibility</li>
* <li><code>COMPILER_COMPLIANCE_CODEGEN_TARGET</code> for unsupported .class format</li>
* </ul>
*/
public static final Pair<Integer, String> checkCompilerCompliance(IJavaProject javaProject) {
// get the project compliance level option
String compliance = javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true);
// check it against a list of valid compliance level strings.
if (!checkCompliance(javaProject, compliance)) {
// if we didn't find the proper compliance level, we return an error
return Pair.of(COMPILER_COMPLIANCE_LEVEL, compliance);
}
// otherwise we check source compatibility
String source = javaProject.getOption(JavaCore.COMPILER_SOURCE, true);
// check it against a list of valid compliance level strings.
if (!checkCompliance(javaProject, source)) {
// if we didn't find the proper compliance level, we return an error
return Pair.of(COMPILER_COMPLIANCE_SOURCE, source);
}
// otherwise check codegen level
String codeGen = javaProject.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true);
// check it against a list of valid compliance level strings.
if (!checkCompliance(javaProject, codeGen)) {
// if we didn't find the proper compliance level, we return an error
return Pair.of(COMPILER_COMPLIANCE_CODEGEN_TARGET, codeGen);
}
return Pair.of(COMPILER_COMPLIANCE_OK, null);
}
/**
* Checks the project compiler compliance level is supported.
* @param project The project to check
* @return A pair with the first integer being an error code, and the second value
* being the invalid value found or null. The error code can be: <ul>
* <li><code>COMPILER_COMPLIANCE_OK</code> if the project is properly configured</li>
* <li><code>COMPILER_COMPLIANCE_LEVEL</code> for unsupported compiler level</li>
* <li><code>COMPILER_COMPLIANCE_SOURCE</code> for unsupported source compatibility</li>
* <li><code>COMPILER_COMPLIANCE_CODEGEN_TARGET</code> for unsupported .class format</li>
* </ul>
*/
public static final Pair<Integer, String> checkCompilerCompliance(IProject project) {
// get the java project from the IProject resource object
IJavaProject javaProject = JavaCore.create(project);
// check and return the result.
return checkCompilerCompliance(javaProject);
}
/**
* Checks, and fixes if needed, the compiler compliance level, and the source compatibility
* level
* @param project The project to check and fix.
*/
public static final void checkAndFixCompilerCompliance(IProject project) {
// FIXME This method is never used. Shall we just removed it?
// {@link #checkAndFixCompilerCompliance(IJavaProject)} is used instead.
// get the java project from the IProject resource object
IJavaProject javaProject = JavaCore.create(project);
// Now we check the compiler compliance level and make sure it is valid
checkAndFixCompilerCompliance(javaProject);
}
/**
* Checks, and fixes if needed, the compiler compliance level, and the source compatibility
* level
* @param javaProject The Java project to check and fix.
*/
public static final void checkAndFixCompilerCompliance(IJavaProject javaProject) {
Pair<Integer, String> result = checkCompilerCompliance(javaProject);
if (result.getFirst().intValue() != COMPILER_COMPLIANCE_OK) {
// setup the preferred compiler compliance level.
javaProject.setOption(JavaCore.COMPILER_COMPLIANCE,
AdtConstants.COMPILER_COMPLIANCE_PREFERRED);
javaProject.setOption(JavaCore.COMPILER_SOURCE,
AdtConstants.COMPILER_COMPLIANCE_PREFERRED);
javaProject.setOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM,
AdtConstants.COMPILER_COMPLIANCE_PREFERRED);
// clean the project to make sure we recompile
try {
javaProject.getProject().build(IncrementalProjectBuilder.CLEAN_BUILD,
new NullProgressMonitor());
} catch (CoreException e) {
AdtPlugin.printErrorToConsole(javaProject.getProject(),
"Project compiler settings changed. Clean your project.");
}
}
}
/**
* Makes the given project use JDK 6 (or more specifically,
* {@link AdtConstants#COMPILER_COMPLIANCE_PREFERRED} as the compilation
* target, regardless of what the default IDE JDK level is, provided a JRE
* of the given level is installed.
*
* @param javaProject the Java project
* @throws CoreException if the IDE throws an exception setting the compiler
* level
*/
@SuppressWarnings("restriction") // JDT API for setting compliance options
public static void enforcePreferredCompilerCompliance(@NonNull IJavaProject javaProject)
throws CoreException {
String compliance = javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true);
if (compliance == null ||
JavaModelUtil.isVersionLessThan(compliance, COMPILER_COMPLIANCE_PREFERRED)) {
IVMInstallType[] types = JavaRuntime.getVMInstallTypes();
for (int i = 0; i < types.length; i++) {
IVMInstallType type = types[i];
IVMInstall[] installs = type.getVMInstalls();
for (int j = 0; j < installs.length; j++) {
IVMInstall install = installs[j];
if (install instanceof IVMInstall2) {
IVMInstall2 install2 = (IVMInstall2) install;
// Java version can be 1.6.0, and preferred is 1.6
if (install2.getJavaVersion().startsWith(COMPILER_COMPLIANCE_PREFERRED)) {
Map<String, String> options = javaProject.getOptions(false);
JavaCore.setComplianceOptions(COMPILER_COMPLIANCE_PREFERRED, options);
JavaModelUtil.setDefaultClassfileOptions(options,
COMPILER_COMPLIANCE_PREFERRED);
javaProject.setOptions(options);
return;
}
}
}
}
}
}
/**
* Returns a {@link IProject} by its running application name, as it returned by the AVD.
* <p/>
* <var>applicationName</var> will in most case be the package declared in the manifest, but
* can, in some cases, be a custom process name declared in the manifest, in the
* <code>application</code>, <code>activity</code>, <code>receiver</code>, or
* <code>service</code> nodes.
* @param applicationName The application name.
* @return a project or <code>null</code> if no matching project were found.
*/
public static IProject findAndroidProjectByAppName(String applicationName) {
// Get the list of project for the current workspace
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IProject[] projects = workspace.getRoot().getProjects();
// look for a project that matches the packageName of the app
// we're trying to debug
for (IProject p : projects) {
if (p.isOpen()) {
try {
if (p.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
// ignore non android projects
continue;
}
} catch (CoreException e) {
// failed to get the nature? skip project.
continue;
}
// check that there is indeed a manifest file.
IFile manifestFile = getManifest(p);
if (manifestFile == null) {
// no file? skip this project.
continue;
}
ManifestData data = AndroidManifestHelper.parseForData(manifestFile);
if (data == null) {
// skip this project.
continue;
}
String manifestPackage = data.getPackage();
if (manifestPackage != null && manifestPackage.equals(applicationName)) {
// this is the project we were looking for!
return p;
} else {
// if the package and application name don't match,
// we look for other possible process names declared in the manifest.
String[] processes = data.getProcesses();
for (String process : processes) {
if (process.equals(applicationName)) {
return p;
}
}
}
}
}
return null;
}
public static void fixProjectNatureOrder(IProject project) throws CoreException {
IProjectDescription description = project.getDescription();
String[] natures = description.getNatureIds();
// if the android nature is not the first one, we reorder them
if (AdtConstants.NATURE_DEFAULT.equals(natures[0]) == false) {
// look for the index
for (int i = 0 ; i < natures.length ; i++) {
if (AdtConstants.NATURE_DEFAULT.equals(natures[i])) {
// if we try to just reorder the array in one pass, this doesn't do
// anything. I guess JDT check that we are actually adding/removing nature.
// So, first we'll remove the android nature, and then add it back.
// remove the android nature
removeNature(project, AdtConstants.NATURE_DEFAULT);
// now add it back at the first index.
description = project.getDescription();
natures = description.getNatureIds();
String[] newNatures = new String[natures.length + 1];
// first one is android
newNatures[0] = AdtConstants.NATURE_DEFAULT;
// next the rest that was before the android nature
System.arraycopy(natures, 0, newNatures, 1, natures.length);
// set the new natures
description.setNatureIds(newNatures);
project.setDescription(description, null);
// and stop
break;
}
}
}
}
/**
* Removes a specific nature from a project.
* @param project The project to remove the nature from.
* @param nature The nature id to remove.
* @throws CoreException
*/
public static void removeNature(IProject project, String nature) throws CoreException {
IProjectDescription description = project.getDescription();
String[] natures = description.getNatureIds();
// check if the project already has the android nature.
for (int i = 0; i < natures.length; ++i) {
if (nature.equals(natures[i])) {
String[] newNatures = new String[natures.length - 1];
if (i > 0) {
System.arraycopy(natures, 0, newNatures, 0, i);
}
System.arraycopy(natures, i + 1, newNatures, i, natures.length - i - 1);
description.setNatureIds(newNatures);
project.setDescription(description, null);
return;
}
}
}
/**
* Returns if the project has error level markers.
* @param includeReferencedProjects flag to also test the referenced projects.
* @throws CoreException
*/
public static boolean hasError(IProject project, boolean includeReferencedProjects)
throws CoreException {
IMarker[] markers = project.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE);
if (markers != null && markers.length > 0) {
// the project has marker(s). even though they are "problem" we
// don't know their severity. so we loop on them and figure if they
// are warnings or errors
for (IMarker m : markers) {
int s = m.getAttribute(IMarker.SEVERITY, -1);
if (s == IMarker.SEVERITY_ERROR) {
return true;
}
}
}
// test the referenced projects if needed.
if (includeReferencedProjects) {
List<IProject> projects = getReferencedProjects(project);
for (IProject p : projects) {
if (hasError(p, false)) {
return true;
}
}
}
return false;
}
/**
* Saves a String property into the persistent storage of a resource.
* @param resource The resource into which the string value is saved.
* @param propertyName the name of the property. The id of the plug-in is added to this string.
* @param value the value to save
* @return true if the save succeeded.
*/
public static boolean saveStringProperty(IResource resource, String propertyName,
String value) {
QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID, propertyName);
try {
resource.setPersistentProperty(qname, value);
} catch (CoreException e) {
return false;
}
return true;
}
/**
* Loads a String property from the persistent storage of a resource.
* @param resource The resource from which the string value is loaded.
* @param propertyName the name of the property. The id of the plug-in is added to this string.
* @return the property value or null if it was not found.
*/
public static String loadStringProperty(IResource resource, String propertyName) {
QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID, propertyName);
try {
String value = resource.getPersistentProperty(qname);
return value;
} catch (CoreException e) {
return null;
}
}
/**
* Saves a property into the persistent storage of a resource.
* @param resource The resource into which the boolean value is saved.
* @param propertyName the name of the property. The id of the plug-in is added to this string.
* @param value the value to save
* @return true if the save succeeded.
*/
public static boolean saveBooleanProperty(IResource resource, String propertyName,
boolean value) {
return saveStringProperty(resource, propertyName, Boolean.toString(value));
}
/**
* Loads a boolean property from the persistent storage of a resource.
* @param resource The resource from which the boolean value is loaded.
* @param propertyName the name of the property. The id of the plug-in is added to this string.
* @param defaultValue The default value to return if the property was not found.
* @return the property value or the default value if the property was not found.
*/
public static boolean loadBooleanProperty(IResource resource, String propertyName,
boolean defaultValue) {
String value = loadStringProperty(resource, propertyName);
if (value != null) {
return Boolean.parseBoolean(value);
}
return defaultValue;
}
public static Boolean loadBooleanProperty(IResource resource, String propertyName) {
String value = loadStringProperty(resource, propertyName);
if (value != null) {
return Boolean.valueOf(value);
}
return null;
}
/**
* Saves the path of a resource into the persistent storage of a resource.
* @param resource The resource into which the resource path is saved.
* @param propertyName the name of the property. The id of the plug-in is added to this string.
* @param value The resource to save. It's its path that is actually stored. If null, an
* empty string is stored.
* @return true if the save succeeded
*/
public static boolean saveResourceProperty(IResource resource, String propertyName,
IResource value) {
if (value != null) {
IPath iPath = value.getFullPath();
return saveStringProperty(resource, propertyName, iPath.toString());
}
return saveStringProperty(resource, propertyName, ""); //$NON-NLS-1$
}
/**
* Loads the path of a resource from the persistent storage of a resource, and returns the
* corresponding IResource object.
* @param resource The resource from which the resource path is loaded.
* @param propertyName the name of the property. The id of the plug-in is added to this string.
* @return The corresponding IResource object (or children interface) or null
*/
public static IResource loadResourceProperty(IResource resource, String propertyName) {
String value = loadStringProperty(resource, propertyName);
if (value != null && value.length() > 0) {
return ResourcesPlugin.getWorkspace().getRoot().findMember(new Path(value));
}
return null;
}
/**
* Returns the list of referenced project that are opened and Java projects.
* @param project
* @return a new list object containing the opened referenced java project.
* @throws CoreException
*/
public static List<IProject> getReferencedProjects(IProject project) throws CoreException {
IProject[] projects = project.getReferencedProjects();
ArrayList<IProject> list = new ArrayList<IProject>();
for (IProject p : projects) {
if (p.isOpen() && p.hasNature(JavaCore.NATURE_ID)) {
list.add(p);
}
}
return list;
}
/**
* Checks a Java project compiler level option against a list of supported versions.
* @param optionValue the Compiler level option.
* @return true if the option value is supported.
*/
private static boolean checkCompliance(@NonNull IJavaProject project, String optionValue) {
for (String s : AdtConstants.COMPILER_COMPLIANCE) {
if (s != null && s.equals(optionValue)) {
return true;
}
}
if (JavaCore.VERSION_1_7.equals(optionValue)) {
// Requires API 19 and buildTools 19
Sdk currentSdk = Sdk.getCurrent();
if (currentSdk != null) {
IProject p = project.getProject();
IAndroidTarget target = currentSdk.getTarget(p);
if (target == null || target.getVersion().getApiLevel() < 19) {
return false;
}
ProjectState projectState = Sdk.getProjectState(p);
if (projectState != null) {
BuildToolInfo buildToolInfo = projectState.getBuildToolInfo();
if (buildToolInfo == null) {
buildToolInfo = currentSdk.getLatestBuildTool();
}
if (buildToolInfo == null || buildToolInfo.getRevision().getMajor() < 19) {
return false;
}
}
return true;
}
}
return false;
}
/**
* Returns the apk filename for the given project
* @param project The project.
* @param config An optional config name. Can be null.
*/
public static String getApkFilename(IProject project, String config) {
if (config != null) {
return project.getName() + "-" + config + SdkConstants.DOT_ANDROID_PACKAGE; //$NON-NLS-1$
}
return project.getName() + SdkConstants.DOT_ANDROID_PACKAGE;
}
/**
* Find the list of projects on which this JavaProject is dependent on at the compilation level.
*
* @param javaProject Java project that we are looking for the dependencies.
* @return A list of Java projects for which javaProject depend on.
* @throws JavaModelException
*/
public static List<IJavaProject> getAndroidProjectDependencies(IJavaProject javaProject)
throws JavaModelException {
String[] requiredProjectNames = javaProject.getRequiredProjectNames();
// Go from java project name to JavaProject name
IJavaModel javaModel = javaProject.getJavaModel();
// loop through all dependent projects and keep only those that are Android projects
List<IJavaProject> projectList = new ArrayList<IJavaProject>(requiredProjectNames.length);
for (String javaProjectName : requiredProjectNames) {
IJavaProject androidJavaProject = javaModel.getJavaProject(javaProjectName);
//Verify that the project has also the Android Nature
try {
if (!androidJavaProject.getProject().hasNature(AdtConstants.NATURE_DEFAULT)) {
continue;
}
} catch (CoreException e) {
continue;
}
projectList.add(androidJavaProject);
}
return projectList;
}
/**
* Returns the android package file as an IFile object for the specified
* project.
* @param project The project
* @return The android package as an IFile object or null if not found.
*/
public static IFile getApplicationPackage(IProject project) {
// get the output folder
IFolder outputLocation = BaseProjectHelper.getAndroidOutputFolder(project);
if (outputLocation == null) {
AdtPlugin.printErrorToConsole(project,
"Failed to get the output location of the project. Check build path properties"
);
return null;
}
// get the package path
String packageName = project.getName() + SdkConstants.DOT_ANDROID_PACKAGE;
IResource r = outputLocation.findMember(packageName);
// check the package is present
if (r instanceof IFile && r.exists()) {
return (IFile)r;
}
String msg = String.format("Could not find %1$s!", packageName);
AdtPlugin.printErrorToConsole(project, msg);
return null;
}
/**
* Returns an {@link IFile} object representing the manifest for the given project.
*
* @param project The project containing the manifest file.
* @return An IFile object pointing to the manifest or null if the manifest
* is missing.
*/
public static IFile getManifest(IProject project) {
IResource r = project.findMember(AdtConstants.WS_SEP
+ SdkConstants.FN_ANDROID_MANIFEST_XML);
if (r == null || r.exists() == false || (r instanceof IFile) == false) {
return null;
}
return (IFile) r;
}
/**
* Does a full release build of the application, including the libraries. Do not build the
* package.
*
* @param project The project to be built.
* @param monitor A eclipse runtime progress monitor to be updated by the builders.
* @throws CoreException
*/
@SuppressWarnings("unchecked")
public static void compileInReleaseMode(IProject project, IProgressMonitor monitor)
throws CoreException {
compileInReleaseMode(project, true /*includeDependencies*/, monitor);
}
/**
* Does a full release build of the application, including the libraries. Do not build the
* package.
*
* @param project The project to be built.
* @param monitor A eclipse runtime progress monitor to be updated by the builders.
* @throws CoreException
*/
@SuppressWarnings("unchecked")
private static void compileInReleaseMode(IProject project, boolean includeDependencies,
IProgressMonitor monitor)
throws CoreException {
if (includeDependencies) {
ProjectState projectState = Sdk.getProjectState(project);
// this gives us all the library projects, direct and indirect dependencies,
// so no need to run this method recursively.
List<IProject> libraries = projectState.getFullLibraryProjects();
// build dependencies in reverse order to prevent libraries being rebuilt
// due to refresh of other libraries (they would be compiled in the wrong mode).
for (int i = libraries.size() - 1 ; i >= 0 ; i--) {
IProject lib = libraries.get(i);
compileInReleaseMode(lib, false /*includeDependencies*/, monitor);
// force refresh of the dependency.
lib.refreshLocal(IResource.DEPTH_INFINITE, monitor);
}
}
// do a full build on all the builders to guarantee that the builders are called.
// (Eclipse does an optimization where builders are not called if there aren't any
// deltas).
ICommand[] commands = project.getDescription().getBuildSpec();
for (ICommand command : commands) {
String name = command.getBuilderName();
if (PreCompilerBuilder.ID.equals(name)) {
Map newArgs = new HashMap();
newArgs.put(PreCompilerBuilder.RELEASE_REQUESTED, "");
if (command.getArguments() != null) {
newArgs.putAll(command.getArguments());
}
project.build(IncrementalProjectBuilder.FULL_BUILD,
PreCompilerBuilder.ID, newArgs, monitor);
} else if (PostCompilerBuilder.ID.equals(name)) {
if (includeDependencies == false) {
// this is a library, we need to build it!
project.build(IncrementalProjectBuilder.FULL_BUILD, name,
command.getArguments(), monitor);
}
} else {
project.build(IncrementalProjectBuilder.FULL_BUILD, name,
command.getArguments(), monitor);
}
}
}
/**
* Force building the project and all its dependencies.
*
* @param project the project to build
* @param kind the build kind
* @param monitor
* @throws CoreException
*/
public static void buildWithDeps(IProject project, int kind, IProgressMonitor monitor)
throws CoreException {
// Get list of projects that we depend on
ProjectState projectState = Sdk.getProjectState(project);
// this gives us all the library projects, direct and indirect dependencies,
// so no need to run this method recursively.
List<IProject> libraries = projectState.getFullLibraryProjects();
// build dependencies in reverse order to prevent libraries being rebuilt
// due to refresh of other libraries (they would be compiled in the wrong mode).
for (int i = libraries.size() - 1 ; i >= 0 ; i--) {
IProject lib = libraries.get(i);
lib.build(kind, monitor);
lib.refreshLocal(IResource.DEPTH_INFINITE, monitor);
}
project.build(kind, monitor);
}
/**
* Build project incrementally, including making the final packaging even if it is disabled
* by default.
*
* @param project The project to be built.
* @param monitor A eclipse runtime progress monitor to be updated by the builders.
* @throws CoreException
*/
public static void doFullIncrementalDebugBuild(IProject project, IProgressMonitor monitor)
throws CoreException {
// Get list of projects that we depend on
List<IJavaProject> androidProjectList = new ArrayList<IJavaProject>();
try {
androidProjectList = getAndroidProjectDependencies(
BaseProjectHelper.getJavaProject(project));
} catch (JavaModelException e) {
AdtPlugin.printErrorToConsole(project, e);
}
// Recursively build dependencies
for (IJavaProject dependency : androidProjectList) {
doFullIncrementalDebugBuild(dependency.getProject(), monitor);
}
// Do an incremental build to pick up all the deltas
project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor);
// If the preferences indicate not to use post compiler optimization
// then the incremental build will have done everything necessary, otherwise,
// we have to run the final builder manually (if requested).
if (AdtPrefs.getPrefs().getBuildSkipPostCompileOnFileSave()) {
// Create the map to pass to the PostC builder
Map<String, String> args = new TreeMap<String, String>();
args.put(PostCompilerBuilder.POST_C_REQUESTED, ""); //$NON-NLS-1$
// call the post compiler manually, forcing FULL_BUILD otherwise Eclipse won't
// call the builder since the delta is empty.
project.build(IncrementalProjectBuilder.FULL_BUILD,
PostCompilerBuilder.ID, args, monitor);
}
// because the post compiler builder does a delayed refresh due to
// library not picking the refresh up if it's done during the build,
// we want to force a refresh here as this call is generally asking for
// a build to use the apk right after the call.
project.refreshLocal(IResource.DEPTH_INFINITE, monitor);
}
}