aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorXavier Ducrohet <xav@android.com>2010-06-29 18:03:32 -0700
committerXavier Ducrohet <xav@android.com>2010-07-02 09:35:10 -0700
commitd10ff719155dc5ea401c1fee284434561672399b (patch)
tree852ef2a7bfdf4bb18bd4af083d016b9863cb9e38
parent1d21469ba4c7a69dc96f264d979c03b8a189a9cf (diff)
downloadsdk-d10ff719155dc5ea401c1fee284434561672399b.zip
sdk-d10ff719155dc5ea401c1fee284434561672399b.tar.gz
sdk-d10ff719155dc5ea401c1fee284434561672399b.tar.bz2
ADT support for libraries depending on libraries.
Major change in the workflow linking projects and library together. This is now done through a single job with a queue of action to do. This ensure that each new opened project is processed one at a time and not in parallel which would generate problems. Change-Id: I2a05ada293a21faba65bb639092f77ff7e5ffb2d
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/MultiApkExportAction.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerBuilder.java6
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PreCompilerBuilder.java5
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectState.java130
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java5
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java914
6 files changed, 728 insertions, 334 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/MultiApkExportAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/MultiApkExportAction.java
index cce8ac8..f5876e9 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/MultiApkExportAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/MultiApkExportAction.java
@@ -244,7 +244,7 @@ public class MultiApkExportAction implements IObjectActionDelegate {
int versionCode, ApkData apk, Entry<String, String> softVariant, IFolder binFolder)
throws CoreException {
// get the libraries for this project
- IProject[] libProjects = projectState.getLibraryProjects();
+ IProject[] libProjects = projectState.getFullLibraryProjects();
IProject project = projectState.getProject();
IJavaProject javaProject = JavaCore.create(project);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerBuilder.java
index ffec6e7..15f960c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerBuilder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerBuilder.java
@@ -206,7 +206,7 @@ public class PostCompilerBuilder extends BaseBuilder {
}
// get the libraries
- libProjects = projectState.getLibraryProjects();
+ libProjects = projectState.getFullLibraryProjects();
IJavaProject javaProject = JavaCore.create(project);
@@ -218,7 +218,7 @@ public class PostCompilerBuilder extends BaseBuilder {
IJavaProject[] referencedJavaProjects = PostCompilerHelper.getJavaProjects(javaProjects);
// mix the java project and the library projects
- final int libCount = libProjects != null ? libProjects.length : 0;
+ final int libCount = libProjects.length;
final int javaCount = javaProjects != null ? javaProjects.length : 0;
allRefProjects = new IProject[libCount + javaCount];
if (libCount > 0) {
@@ -268,7 +268,7 @@ public class PostCompilerBuilder extends BaseBuilder {
// if the main resources didn't change, then we check for the library
// ones (will trigger resource repackaging too)
if ((mPackageResources == false || mBuildFinalPackage == false) &&
- libProjects != null && libProjects.length > 0) {
+ libProjects.length > 0) {
for (IProject libProject : libProjects) {
delta = getDelta(libProject);
if (delta != null) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PreCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PreCompilerBuilder.java
index 868ecf7..e3a6715 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PreCompilerBuilder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PreCompilerBuilder.java
@@ -234,7 +234,7 @@ public class PreCompilerBuilder extends BaseBuilder {
IAndroidTarget projectTarget = projectState.getTarget();
// get the libraries
- libProjects = projectState.getLibraryProjects();
+ libProjects = projectState.getFullLibraryProjects();
IJavaProject javaProject = JavaCore.create(project);
@@ -287,8 +287,7 @@ public class PreCompilerBuilder extends BaseBuilder {
// if the main resources didn't change, then we check for the library
// ones (will trigger resource recompilation too)
- if (mMustCompileResources == false && libProjects != null &&
- libProjects.length > 0) {
+ if (mMustCompileResources == false && libProjects.length > 0) {
for (IProject libProject : libProjects) {
delta = getDelta(libProject);
if (delta != null) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectState.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectState.java
index 93c77f9..5886692 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectState.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectState.java
@@ -42,6 +42,18 @@ import java.util.regex.Matcher;
* <p>This gives raw access to the properties (from <code>default.properties</code>), as well
* as direct access to target, apksettings and library information.
*
+ * This also gives access to library information.
+ *
+ * {@link #isLibrary()} indicates if the project is a library.
+ * {@link #hasLibraries()} and {@link #getLibraries()} give access to the libraries through
+ * instances of {@link LibraryState}. A {@link LibraryState} instance is a link between a main
+ * project and its library. Theses instances are owned by the {@link ProjectState}.
+ *
+ * {@link #isMissingLibraries()} will indicate if the project has libraries that are not resolved.
+ * Unresolved libraries are libraries that do not have any matching opened Eclipse project.
+ * When there are missing libraries, the {@link LibraryState} instance for them will return null
+ * for {@link LibraryState#getProjectState()}.
+ *
*/
public final class ProjectState {
@@ -74,13 +86,14 @@ public final class ProjectState {
* Closes the library. This resets the IProject from this object ({@link #getProjectState()} will
* return <code>null</code>), and updates the main project data so that the library
* {@link IProject} object does not show up in the return value of
- * {@link ProjectState#getLibraryProjects()}.
+ * {@link ProjectState#getFullLibraryProjects()}.
*/
public void close() {
+ mProjectState.removeParentProject(getMainProjectState());
mProjectState = null;
mPath = null;
- updateLibraries();
+ getMainProjectState().updateFullLibraryList();
}
private void setRelativePath(String relativePath) {
@@ -90,8 +103,9 @@ public final class ProjectState {
private void setProject(ProjectState project) {
mProjectState = project;
mPath = project.getProject().getLocation().toOSString();
+ mProjectState.addParentProject(getMainProjectState());
- updateLibraries();
+ getMainProjectState().updateFullLibraryList();
}
/**
@@ -145,15 +159,22 @@ public final class ProjectState {
private final IProject mProject;
private final ProjectProperties mProperties;
+ private IAndroidTarget mTarget;
+ private ApkSettings mApkSettings;
/**
* list of libraries. Access to this list must be protected by
* <code>synchronized(mLibraries)</code>, but it is important that such code do not call
* out to other classes (especially those protected by {@link Sdk#getLock()}.)
*/
private final ArrayList<LibraryState> mLibraries = new ArrayList<LibraryState>();
- private IAndroidTarget mTarget;
- private ApkSettings mApkSettings;
- private IProject[] mLibraryProjects;
+ /** Cached list of all IProject instances representing the resolved libraries, including
+ * indirect dependencies. This must never be null. */
+ private IProject[] mLibraryProjects = new IProject[0];
+ /**
+ * List of parent projects. When this instance is a library ({@link #isLibrary()} returns
+ * <code>true</code>) then this is filled with projects that depends on this project.
+ */
+ private final ArrayList<ProjectState> mParentProjects = new ArrayList<ProjectState>();
public ProjectState(IProject project, ProjectProperties properties) {
mProject = project;
@@ -281,7 +302,7 @@ public final class ProjectState {
diff.removed.addAll(oldLibraries);
// update the library with what IProjet are known at the time.
- updateLibraries();
+ updateFullLibraryList();
}
return diff;
@@ -305,13 +326,14 @@ public final class ProjectState {
}
/**
- * Convenience method returning all the IProject objects for the resolved libraries.
+ * Returns all the <strong>resolved</strong> library projects, including indirect dependencies.
+ * The array is ordered to match the library priority order for resource processing with
+ * <code>aapt</code>.
* <p/>If some dependencies are not resolved (or their projects is not opened in Eclipse),
* they will not show up in this list.
- * @return the resolved projects or null if there are no project (either no resolved or no
- * dependencies)
+ * @return the resolved projects. May be an empty list.
*/
- public IProject[] getLibraryProjects() {
+ public IProject[] getFullLibraryProjects() {
return mLibraryProjects;
}
@@ -369,6 +391,15 @@ public final class ProjectState {
return null;
}
+ /**
+ * Returns the {@link LibraryState} object for a given <var>name</var>.
+ * </p>This can only return a non-null object if the link between the main project's
+ * {@link IProject} and the library's {@link IProject} was done.
+ *
+ * @return the matching LibraryState or <code>null</code>
+ *
+ * @see #needs(IProject)
+ */
public LibraryState getLibrary(String name) {
synchronized (mLibraries) {
for (LibraryState state : mLibraries) {
@@ -424,6 +455,26 @@ public final class ProjectState {
}
/**
+ * Returns whether the project depends on a given <var>library</var>
+ * @param library the library to check.
+ * @return true if the project depends on the library. This is not affected by whether the link
+ * was done through {@link #needs(ProjectState)}.
+ */
+ public boolean dependsOn(ProjectState library) {
+ synchronized (mLibraries) {
+ for (LibraryState state : mLibraries) {
+ if (state != null && state.getProjectState() != null &&
+ library.getProject().equals(state.getProjectState().getProject())) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+
+ /**
* Updates a library with a new path.
* <p/>This method acts both as a check and an action. If the project does not depend on the
* given <var>oldRelativePath</var> then no action is done and <code>null</code> is returned.
@@ -491,6 +542,15 @@ public final class ProjectState {
return null;
}
+
+ private void addParentProject(ProjectState parentState) {
+ mParentProjects.add(parentState);
+ }
+
+ private void removeParentProject(ProjectState parentState) {
+ mParentProjects.remove(parentState);
+ }
+
/**
* Saves the default.properties file and refreshes it to make sure that it gets reloaded
* by Eclipse
@@ -543,20 +603,51 @@ public final class ProjectState {
return null;
}
- private void updateLibraries() {
+ /**
+ * Update the full library list, including indirect dependencies. The result is returned by
+ * {@link #getFullLibraryProjects()}.
+ */
+ private void updateFullLibraryList() {
ArrayList<IProject> list = new ArrayList<IProject>();
synchronized (mLibraries) {
- for (LibraryState state : mLibraries) {
- if (state.getProjectState() != null) {
- list.add(state.getProjectState().getProject());
- }
- }
+ buildFullLibraryDependencies(mLibraries, list);
}
mLibraryProjects = list.toArray(new IProject[list.size()]);
}
/**
+ * Resolves a given list of libraries, finds out if they depend on other libraries, and
+ * returns a full list of all the direct and indirect dependencies in the proper order (first
+ * is higher priority when calling aapt).
+ * @param inLibraries the libraries to resolve
+ * @param outLibraries where to store all the libraries.
+ */
+ private void buildFullLibraryDependencies(List<LibraryState> inLibraries,
+ ArrayList<IProject> outLibraries) {
+ // loop in the inverse order to resolve dependencies on the libraries, so that if a library
+ // is required by two higher level libraries it can be inserted in the correct place
+ for (int i = inLibraries.size() - 1 ; i >= 0 ; i--) {
+ LibraryState library = inLibraries.get(i);
+
+ // get its libraries if possible
+ ProjectState libProjectState = library.getProjectState();
+ if (libProjectState != null) {
+ List<LibraryState> dependencies = libProjectState.getLibraries();
+
+ // build the dependencies for those libraries
+ buildFullLibraryDependencies(dependencies, outLibraries);
+
+ // and add the current library (if needed) in front (higher priority)
+ if (outLibraries.contains(libProjectState.getProject()) == false) {
+ outLibraries.add(0, libProjectState.getProject());
+ }
+ }
+ }
+ }
+
+
+ /**
* Converts a path containing only / by the proper platform separator.
*/
private String convertPath(String path) {
@@ -589,4 +680,9 @@ public final class ProjectState {
public int hashCode() {
return mProject.hashCode();
}
+
+ @Override
+ public String toString() {
+ return mProject.getName();
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java
index ef2e950..e46c964 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java
@@ -384,7 +384,7 @@ public class ProjectResources implements IResourceRepository {
if (mProject != null) {
ProjectState state = Sdk.getProjectState(mProject);
if (state != null) {
- IProject[] libraries = state.getLibraryProjects();
+ IProject[] libraries = state.getFullLibraryProjects();
ResourceManager resMgr = ResourceManager.getInstance();
@@ -392,8 +392,7 @@ public class ProjectResources implements IResourceRepository {
// one will have priority over the 2nd one. So it's better to loop in the inverse
// order and fill the map with resources that will be overwritten by higher
// priority resources
- final int libCount = libraries != null ? libraries.length : 0;
- for (int i = libCount - 1 ; i >= 0 ; i--) {
+ for (int i = libraries.length - 1 ; i >= 0 ; i--) {
IProject library = libraries[i];
ProjectResources libRes = resMgr.getProjectResources(library);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
index c3dc894..a4c216c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
@@ -27,6 +27,7 @@ import com.android.ide.eclipse.adt.internal.project.ProjectState.LibraryState;
import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor;
import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener;
import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IProjectListener;
+import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IResourceEventListener;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData.LayoutBridge;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.AndroidVersion;
@@ -48,20 +49,17 @@ import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IWorkspaceRoot;
-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.IStatus;
-import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
-import org.eclipse.ui.progress.IJobRunnable;
import java.io.File;
import java.io.IOException;
@@ -71,6 +69,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
@@ -605,6 +604,7 @@ public final class Sdk {
GlobalProjectMonitor monitor = GlobalProjectMonitor.getMonitor();
monitor.addProjectListener(mProjectListener);
monitor.addFileListener(mFileListener, IResourceDelta.CHANGED | IResourceDelta.ADDED);
+ monitor.addResourceEventListener(mResourceEventListener);
// pre-compute some paths
mDocBaseUrl = getDocumentationBaseUrl(mManager.getLocation() +
@@ -631,6 +631,7 @@ public final class Sdk {
GlobalProjectMonitor monitor = GlobalProjectMonitor.getMonitor();
monitor.removeProjectListener(mProjectListener);
monitor.removeFileListener(mFileListener);
+ monitor.removeResourceEventListener(mResourceEventListener);
// the IAndroidTarget objects are now obsolete so update the project states.
synchronized (sLock) {
@@ -736,6 +737,9 @@ public final class Sdk {
// 2. if the project is a library, make sure to update the
// LibraryState for any main project using this.
+ // Also, record the updated projects that are libraries, to update
+ // projects that depend on them.
+ ArrayList<ProjectState> updatedLibraries = new ArrayList<ProjectState>();
for (ProjectState projectState : sProjectStateMap.values()) {
LibraryState libState = projectState.getLibrary(project);
if (libState != null) {
@@ -749,7 +753,11 @@ public final class Sdk {
// edit the project to remove the linked source folder.
// this also calls LibraryState.close();
- unlinkLibrary(projectState, project, true /*doInJob*/);
+ startActionBundle(new UnlinkLibraryBundle(projectState, project));
+
+ if (projectState.isLibrary()) {
+ updatedLibraries.add(projectState);
+ }
}
}
@@ -760,71 +768,79 @@ public final class Sdk {
// now remove the project for the project map.
sProjectStateMap.remove(project);
+
+ // update the projects that depend on the updated project
+ updateProjectsWithNewLibraries(updatedLibraries);
}
}
}
public void projectOpened(IProject project) {
- onProjectOpened(project, true /*recompile*/);
+ onProjectOpened(project);
}
public void projectOpenedWithWorkspace(IProject project) {
// no need to force recompilation when projects are opened with the workspace.
- onProjectOpened(project, false /*recompile*/);
+ onProjectOpened(project);
}
- private void onProjectOpened(IProject openedProject, boolean recompile) {
+ private void onProjectOpened(final IProject openedProject) {
ProjectState openedState = getProjectState(openedProject);
if (openedState != null) {
- // find dependencies, if any
- if (openedState.isMissingLibraries()) {
- // the opened project depends on some libraries.
- // Look for all other opened projects to see if any is a library for it.
- // if they are, fix its LibraryState to get the IProject reference and
- // link the two projects with the linked source folder.
- boolean foundLibrary = false;
+ if (openedState.hasLibraries()) {
+ // list of library to link to the opened project.
+ final ArrayList<IProject> libsToLink = new ArrayList<IProject>();
+
+ // Look for all other opened projects to see if any is a library for the opened
+ // project.
synchronized (sLock) {
for (ProjectState projectState : sProjectStateMap.values()) {
if (projectState != openedState) {
+ // ProjectState#needs() both checks if this is a missing library
+ // and updates LibraryState to contains the new values.
LibraryState libState = openedState.needs(projectState);
if (libState != null) {
- foundLibrary = true;
- linkProjectAndLibrary(openedState, libState, null,
- true /*doInJob*/);
+ // we have a match! Add the library to the list (if it was
+ // not added through an indirect dependency before).
+ IProject libProject = libState.getProjectState().getProject();
+ if (libsToLink.contains(libProject) == false) {
+ libsToLink.add(libProject);
+ }
+
+ // now find what this depends on, and add it too.
+ // The order here doesn't matter
+ // as it's just to add the linked source folder, so there's no
+ // need to use ProjectState#getFullLibraryProjects() which
+ // could return project that have already been added anyway.
+ fillProjectDependenciesList(libState.getProjectState(),
+ libsToLink);
}
}
}
}
- // force a recompile of the main project through a job
- // (tree is locked)
- if (recompile && foundLibrary) {
- recompile(openedState.getProject());
+ if (libsToLink.size() > 0) {
+ // link the libraries to the opened project through the job by adding an
+ // action bundle to the queue.
+ LinkLibraryBundle bundle = new LinkLibraryBundle();
+ bundle.mProject = openedProject;
+ bundle.mLibraryProjects = libsToLink.toArray(
+ new IProject[libsToLink.size()]);
+ bundle.mPreviousLibraryPath = null;
+ bundle.mCleanupCPE = true;
+ startActionBundle(bundle);
}
}
- // if the project is a library, then try to see if it's required by other projects.
+ // if the project is a library, then add it to the list of projects being opened.
+ // They will be processed in IResourceEventListener#resourceChangeEventEnd.
+ // This is done so that we are sure to process all the projects being opened
+ // first and only then process projects depending on the projects that were opened.
if (openedState.isLibrary()) {
setupLibraryProject(openedProject);
- synchronized (sLock) {
- for (ProjectState projectState : sProjectStateMap.values()) {
- if (projectState != openedState && projectState.isMissingLibraries()) {
- LibraryState libState = projectState.needs(openedState);
- if (libState != null) {
- linkProjectAndLibrary(projectState, libState, null,
- true /*doInJob*/);
-
- // force a recompile of the main project through a job
- // (tree is locked)
- if (recompile) {
- recompile(projectState.getProject());
- }
- }
- }
- }
- }
+ mOpenedLibraryProjects.add(openedState);
}
}
}
@@ -853,12 +869,12 @@ public final class Sdk {
oldRelativePath.toString(), newRelativePath.toString(),
renamedState);
if (libState != null) {
- linkProjectAndLibrary(projectState, libState, from,
- true /*doInJob*/);
-
- // force a recompile of the main project through a job
- // (tree is locked)
- recompile(projectState.getProject());
+ LinkLibraryBundle bundle = new LinkLibraryBundle();
+ bundle.mProject = projectState.getProject();
+ bundle.mLibraryProjects = new IProject[] {
+ libState.getProjectState().getProject() };
+ bundle.mCleanupCPE = false;
+ startActionBundle(bundle);
}
}
}
@@ -896,10 +912,11 @@ public final class Sdk {
// reload the libraries if needed
if (diff.hasDiff()) {
for (LibraryState removedState : diff.removed) {
- ProjectState removePState = removedState.getProjectState();
- if (removePState != null) {
- unlinkLibrary(state, removePState.getProject(),
- false /*doInJob*/);
+ ProjectState removedPState = removedState.getProjectState();
+ if (removedPState != null) {
+ startActionBundle(
+ new UnlinkLibraryBundle(
+ state, removedPState.getProject()));
}
}
@@ -910,16 +927,21 @@ public final class Sdk {
LibraryState libState = state.needs(projectState);
if (libState != null) {
- linkProjectAndLibrary(state, libState, null,
- false /*doInJob*/);
+ IProject[] libArray = new IProject[] {
+ libState.getProjectState().getProject()
+ };
+ LinkLibraryBundle bundle =
+ new LinkLibraryBundle();
+ bundle.mProject = iProject;
+ bundle.mLibraryProjects = libArray;
+ bundle.mPreviousLibraryPath = null;
+ bundle.mCleanupCPE = false;
+ startActionBundle(bundle);
}
}
}
}
}
-
- // need to force a full recompile.
- iProject.build(IncrementalProjectBuilder.FULL_BUILD, monitor);
}
// apply the new target if needed.
@@ -948,6 +970,202 @@ public final class Sdk {
}
};
+ /** List of opened project. This is filled in {@link IProjectListener#projectOpened(IProject)}
+ * and {@link IProjectListener#projectOpenedWithWorkspace(IProject)}, and processed in
+ * {@link IResourceEventListener#resourceChangeEventEnd()}.
+ */
+ private final ArrayList<ProjectState> mOpenedLibraryProjects = new ArrayList<ProjectState>();
+
+ /**
+ * Delegate listener for resource changes. This is called before and after any calls to the
+ * project and file listeners (for a given resource change event).
+ */
+ private IResourceEventListener mResourceEventListener = new IResourceEventListener() {
+ public void resourceChangeEventStart() {
+ // pass
+ }
+
+ public void resourceChangeEventEnd() {
+ updateProjectsWithNewLibraries(mOpenedLibraryProjects);
+ mOpenedLibraryProjects.clear();
+ }
+ };
+
+ /**
+ * Action Bundle to be used with {@link Sdk#startActionBundle(ActionBundle)}.
+ */
+ private interface ActionBundle {
+ enum BundleType { LINK_LIBRARY, UNLINK_LIBRARY };
+ BundleType getType();
+ };
+
+ /**
+ * Action bundle to link libraries to a project.
+ *
+ * @see Sdk#linkProjectAndLibrary(LinkLibraryBundle, IProgressMonitor)
+ */
+ private static class LinkLibraryBundle implements ActionBundle {
+
+ /** The main project receiving the library links. */
+ IProject mProject;
+ /** The libraries to add to the main project. */
+ IProject[] mLibraryProjects;
+ /** an optional old library path that needs to be removed at the same time as the new
+ * libraries are added. Can be <code>null</code> in which case no libraries are removed. */
+ IPath mPreviousLibraryPath;
+ /** Whether unknown IClasspathEntry (that were flagged as being added by ADT) are to be
+ * removed. This is typically only set to <code>true</code> when the project is opened. */
+ boolean mCleanupCPE;
+
+ public BundleType getType() {
+ return BundleType.LINK_LIBRARY;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("LinkLibraryBundle: %1$s (%2$s) > %3$s", //$NON-NLS-1$
+ mProject.getName(),
+ mCleanupCPE,
+ Arrays.toString(mLibraryProjects));
+ }
+ }
+
+ /**
+ * Action bundle to unlink a library from a project.
+ *
+ * @see Sdk#unlinkLibrary(UnlinkLibraryBundle, IProgressMonitor)
+ */
+ private static class UnlinkLibraryBundle implements ActionBundle {
+ /** the main project */
+ final ProjectState mProject;
+ /** the library to remove */
+ final IProject mLibrary;
+
+ UnlinkLibraryBundle(ProjectState project, IProject library) {
+ mProject = project;
+ mLibrary = library;
+ }
+
+ public BundleType getType() {
+ return BundleType.UNLINK_LIBRARY;
+ }
+ }
+
+ private final ArrayList<ActionBundle> mActionBundleQueue = new ArrayList<ActionBundle>();
+
+ /**
+ * Runs the given action bundle through a job queue.
+ *
+ * All action bundles are executed in a job in the exact order they are added.
+ * This is convenient when several actions must be executed in a job consecutively (instead
+ * of in parallel as it would happen if each started its own job) but it is impossible
+ * to manually control the job that's running them (for instance each action is started from
+ * different callbacks such as {@link IProjectListener#projectOpened(IProject)}.
+ *
+ * If the job is not yet started, or has terminated due to lack of action bundle, it is
+ * restarted.
+ *
+ * @param bundle the action bundle to execute
+ */
+ private void startActionBundle(ActionBundle bundle) {
+ boolean startJob = false;
+ synchronized (mActionBundleQueue) {
+ startJob = mActionBundleQueue.size() == 0;
+ mActionBundleQueue.add(bundle);
+ }
+
+ if (startJob) {
+ Job job = new Job("Android Library Job") { //$NON-NLS-1$
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ // loop until there's no bundle to process
+ while (true) {
+ // get the bundle, but don't remove until we're done, or a new job could be
+ // started.
+ ActionBundle bundle = null;
+ synchronized (mActionBundleQueue) {
+ // there is always a bundle at this point, as they are only removed
+ // at the end of this method, and the job is only started after adding
+ // one
+ bundle = mActionBundleQueue.get(0);
+ }
+
+ // process the bundle.
+ try {
+ switch (bundle.getType()) {
+ case LINK_LIBRARY:
+ linkProjectAndLibrary((LinkLibraryBundle)bundle, monitor);
+ break;
+ case UNLINK_LIBRARY:
+ unlinkLibrary((UnlinkLibraryBundle) bundle, monitor);
+ break;
+ }
+ } catch (Exception e) {
+ AdtPlugin.log(e, "Failed to process bundle: %1$s", //$NON-NLS-1$
+ bundle.toString());
+ }
+
+ // remove it from the list.
+ synchronized (mActionBundleQueue) {
+ mActionBundleQueue.remove(0);
+
+ // no more bundle to process? done.
+ if (mActionBundleQueue.size() == 0) {
+ return Status.OK_STATUS;
+ }
+ }
+ }
+ }
+ };
+ job.setPriority(Job.BUILD);
+ job.schedule();
+ }
+ }
+
+
+ /**
+ * Adds to a list the resolved {@link IProject} dependencies for a given {@link ProjectState}.
+ * This recursively goes down to indirect dependencies.
+ *
+ * <strong>The list is filled in an order that is not valid for calling <code>aapt</code>
+ * </strong>.
+ * Use {@link ProjectState#getFullLibraryProjects()} for use with <code>aapt</code>.
+ *
+ * @param projectState the ProjectState of the project from which to add the libraries.
+ * @param libraries the list of {@link IProject} to fill.
+ */
+ private void fillProjectDependenciesList(ProjectState projectState,
+ ArrayList<IProject> libraries) {
+ for (LibraryState libState : projectState.getLibraries()) {
+ ProjectState libProjectState = libState.getProjectState();
+
+ // only care if the LibraryState has a resolved ProjectState
+ if (libProjectState != null) {
+ // try not to add duplicate. This can happen if a project depends on 2 different
+ // libraries that both depend on the same one.
+ IProject libProject = libProjectState.getProject();
+ if (libraries.contains(libProject) == false) {
+ libraries.add(libProject);
+ }
+
+ // process the libraries of this library too.
+ fillProjectDependenciesList(libProjectState, libraries);
+ }
+ }
+ }
+
+ /**
+ * Sets up a path variable for a given project.
+ * The name of the variable is based on the name of the project. However some valid character
+ * for project names can be invalid for variable paths.
+ * {@link #getLibraryVariableName(String)} return the name of the variable based on the
+ * project name.
+ *
+ * @param libProject the project
+ *
+ * @see IPathVariableManager
+ * @see #getLibraryVariableName(String)
+ */
private void setupLibraryProject(IProject libProject) {
// if needed add a path var for this library
IPathVariableManager pathVarMgr =
@@ -968,15 +1186,28 @@ public final class Sdk {
}
+ /**
+ * Deletes the path variable that was setup for the given project.
+ * @param project the project
+ * @see #disposeLibraryProject(String)
+ */
private void disposeLibraryProject(IProject project) {
disposeLibraryProject(project.getName());
}
- private void disposeLibraryProject(String libName) {
+ /**
+ * Deletes the path variable that was setup for the given project name.
+ * The name of the variable is based on the name of the project. However some valid character
+ * for project names can be invalid for variable paths.
+ * {@link #getLibraryVariableName(String)} return the name of the variable based on the
+ * project name.
+ * @param projectName the name of the project, unmodified.
+ */
+ private void disposeLibraryProject(String projectName) {
IPathVariableManager pathVarMgr =
ResourcesPlugin.getWorkspace().getPathVariableManager();
- final String varName = getLibraryVariableName(libName);
+ final String varName = getLibraryVariableName(projectName);
// remove the value by setting the value to null.
try {
@@ -997,178 +1228,209 @@ public final class Sdk {
}
/**
- * Links a project and a library so that the project can use the library code and resources.
- * <p/>This can be done in a job in case the workspace is not locked for resource
- * modification. See <var>doInJob</var>.
+ * Links a project and a set of libraries so that the project can use the library code.
+ *
+ * This does the follow:
+ * - add the library projects to the main projects dynamic reference list. This is used by
+ * the builders to receive resource change deltas for library projects and figure out what
+ * needs to be recompiled/recreated.
+ * - create new {@link IClasspathEntry} of type {@link IClasspathEntry#CPE_SOURCE} for each
+ * source folder for each library project. If there was a previous
+ * - If {@link LinkLibraryBundle#mCleanupCPE} is set to true, all CPE created by ADT that cannot
+ * be resolved are removed. This should only be used when the project is opened.
*
- * @param projectState the {@link ProjectState} for the main project
- * @param libraryState the {@link LibraryState} for the library project.
- * @param previousLibraryPath an optional old library path that needs to be removed at the
- * same time. Can be <code>null</code> in which case no libraries are removed.
- * @param doInJob whether the action must be done in a new {@link Job}.
+ * @param bundle The {@link LinkLibraryBundle} action bundle that contains all the parameters
+ * necessary to execute the action.
+ * @param monitor an {@link IProgressMonitor}.
+ * @return an {@link IStatus} with the status of the action.
*/
- private void linkProjectAndLibrary(
- final ProjectState projectState,
- final LibraryState libraryState,
- final IPath previousLibraryPath,
- boolean doInJob) {
- final IJobRunnable jobRunnable = new IJobRunnable() {
- public IStatus run(final IProgressMonitor monitor) {
- try {
- IProject project = projectState.getProject();
- IProject library = libraryState.getProjectState().getProject();
-
- // add the library to the list of dynamic references
- IProjectDescription projectDescription = project.getDescription();
- IProject[] refs = projectDescription.getDynamicReferences();
-
- if (refs.length > 0) {
- ArrayList<IProject> list = new ArrayList<IProject>(Arrays.asList(refs));
-
- // remove a previous library if needed (in case of a rename)
- if (previousLibraryPath != null) {
- final int count = list.size();
- for (int i = 0 ; i < count ; i++) {
- // since project basically have only one segment that matter,
- // just check the names
- if (list.get(i).getName().equals(
- previousLibraryPath.lastSegment())) {
- list.remove(i);
- break;
- }
- }
-
+ private IStatus linkProjectAndLibrary(LinkLibraryBundle bundle, IProgressMonitor monitor) {
+ try {
+ // add the library to the list of dynamic references. This is necessary to receive
+ // notifications that the library content changed in the builders.
+ IProjectDescription projectDescription = bundle.mProject.getDescription();
+ IProject[] refs = projectDescription.getDynamicReferences();
+
+ if (refs.length > 0) {
+ ArrayList<IProject> list = new ArrayList<IProject>(Arrays.asList(refs));
+
+ // remove a previous library if needed (in case of a rename)
+ if (bundle.mPreviousLibraryPath != null) {
+ final int count = list.size();
+ for (int i = 0 ; i < count ; i++) {
+ // since project basically have only one segment that matter,
+ // just check the names
+ if (list.get(i).getName().equals(
+ bundle.mPreviousLibraryPath.lastSegment())) {
+ list.remove(i);
+ break;
}
+ }
- // add the new one.
- list.add(library);
+ }
- // set the changed list
- projectDescription.setDynamicReferences(
- list.toArray(new IProject[list.size()]));
- } else {
- projectDescription.setDynamicReferences(new IProject[] { library });
- }
+ // add the new ones.
+ list.addAll(Arrays.asList(bundle.mLibraryProjects));
- // add a linked resource for the source of the library and add it to the project
- final String libName = library.getName();
- final String varName = getLibraryVariableName(libName);
-
- // get the current classpath entries for the project to add the new source
- // folders.
- IJavaProject javaProject = JavaCore.create(project);
- IClasspathEntry[] entries = javaProject.getRawClasspath();
- ArrayList<IClasspathEntry> classpathEntries = new ArrayList<IClasspathEntry>(
- Arrays.asList(entries));
-
- IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
-
- // list to hold the source folder to delete, as they can't be delete before
- // they have been removed from the classpath entries
- final ArrayList<IResource> toDelete = new ArrayList<IResource>();
-
- // loop on the classpath entries and look for CPE_SOURCE entries that
- // are linked folders. If they are created by us for the given library, then
- // we remove them as they'll be created again later (it's easier than trying
- // to keep old one--if they link to the same resource)
- for (int i = 0 ; i < classpathEntries.size();) {
- IClasspathEntry classpathEntry = classpathEntries.get(i);
- if (classpathEntry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
- IPath path = classpathEntry.getPath();
- IResource linkedRes = wsRoot.findMember(path);
- if (linkedRes != null && linkedRes.isLinked() && CREATOR_ADT.equals(
- ProjectHelper.loadStringProperty(linkedRes, PROP_CREATOR))) {
- IResource originalLibrary = ProjectHelper.loadResourceProperty(
- linkedRes, PROP_LIBRARY);
-
- // if the library is missing, or if the library is the one being
- // added or the same path as the one being removed:
- // remove the classpath entry and delete the linked folder.
- if (originalLibrary == null || originalLibrary.equals(library) ||
- originalLibrary.getFullPath().equals(previousLibraryPath)) {
- classpathEntries.remove(i);
- toDelete.add(linkedRes);
- continue; // don't increment i
- }
- }
+ // set the changed list
+ projectDescription.setDynamicReferences(
+ list.toArray(new IProject[list.size()]));
+ } else {
+ projectDescription.setDynamicReferences(bundle.mLibraryProjects);
+ }
+
+ // get the current classpath entries for the project to add the new source
+ // folders.
+ IJavaProject javaProject = JavaCore.create(bundle.mProject);
+ IClasspathEntry[] entries = javaProject.getRawClasspath();
+ ArrayList<IClasspathEntry> classpathEntries = new ArrayList<IClasspathEntry>(
+ Arrays.asList(entries));
+
+ IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
+
+ // loop on the classpath entries and look for CPE_SOURCE entries that
+ // are linked folders, then record them for comparison layer as we add the new
+ // ones.
+ ArrayList<IClasspathEntry> libCpeList = new ArrayList<IClasspathEntry>();
+ if (bundle.mCleanupCPE) {
+ for (IClasspathEntry classpathEntry : classpathEntries) {
+ if (classpathEntry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
+ IPath path = classpathEntry.getPath();
+ IResource linkedRes = wsRoot.findMember(path);
+ if (linkedRes != null && linkedRes.isLinked() &&
+ CREATOR_ADT.equals(ProjectHelper.loadStringProperty(
+ linkedRes, PROP_CREATOR))) {
+ libCpeList.add(classpathEntry);
}
+ }
+ }
+ }
- i++;
+ // loop on the projects to add.
+ for (IProject library : bundle.mLibraryProjects) {
+ final String libName = library.getName();
+ final String varName = getLibraryVariableName(libName);
+
+ // get the list of source folders for the library.
+ ArrayList<IPath> sourceFolderPaths = BaseProjectHelper.getSourceClasspaths(
+ library);
+
+ // loop on all the source folder, ignoring FD_GEN and add them
+ // as linked folder
+ for (IPath sourceFolderPath : sourceFolderPaths) {
+ IResource sourceFolder = wsRoot.findMember(sourceFolderPath);
+ if (sourceFolder == null || sourceFolder.isLinked()) {
+ continue;
}
- // get the list of source folders for the library.
- ArrayList<IPath> sourceFolderPaths = BaseProjectHelper.getSourceClasspaths(
- library);
+ IPath relativePath = sourceFolder.getProjectRelativePath();
+ if (SdkConstants.FD_GEN_SOURCES.equals(relativePath.toString())) {
+ continue;
+ }
- // loop on all the source folder, ignoring FD_GEN and add them as linked folder
- for (IPath sourceFolderPath : sourceFolderPaths) {
- IResource sourceFolder = wsRoot.findMember(sourceFolderPath);
- if (sourceFolder == null) {
- continue;
- }
+ // create the linked path
+ IPath linkedPath = new Path(varName).append(relativePath);
- IPath relativePath = sourceFolder.getProjectRelativePath();
- if (SdkConstants.FD_GEN_SOURCES.equals(relativePath.toString())) {
- continue;
- }
+ // look for an existing linked path
+ IClasspathEntry match = findClasspathEntryMatch(libCpeList, linkedPath, null);
+ if (match == null) {
+
+ // no match, create one
// get a string version, to make up the linked folder name
String srcFolderName = relativePath.toString().replace("/", //$NON-NLS-1$
"_"); //$NON-NLS-1$
+ // folder name
+ String folderName = libName + "_" + srcFolderName; //$NON-NLS-1$
+
// create a linked resource for the library using the path var.
- IFolder libSrc = project.getFolder(libName + "_" + srcFolderName); //$NON-NLS-1$
+ IFolder libSrc = bundle.mProject.getFolder(folderName);
+ IPath libSrcPath = libSrc.getFullPath();
+
+ // check if there's a CPE that would conflict, in which case it needs to
+ // be removed (this can happen for existing CPE that don't match an open
+ // project)
+ match = findClasspathEntryMatch(classpathEntries, null/*rawPath*/,
+ libSrcPath);
+ if (match != null) {
+ classpathEntries.remove(match);
+ }
- libSrc.createLink(new Path(varName).append(relativePath),
+ // the path of the linked resource is based on the path variable
+ // representing the library project, followed by the source folder name.
+ libSrc.createLink(linkedPath,
IResource.REPLACE, monitor);
- // if we were deleting something called exactly the same (which could
- // have linked to a different folder), we remove it from the list
- // of items to delete
- if (toDelete.contains(libSrc)) {
- toDelete.remove(libSrc);
- }
-
- // set some persistent properties on it to know that it was created by ADT.
+ // set some persistent properties on it to know that it was
+ // created by ADT.
ProjectHelper.saveStringProperty(libSrc, PROP_CREATOR, CREATOR_ADT);
ProjectHelper.saveResourceProperty(libSrc, PROP_LIBRARY, library);
// add the source folder to the classpath entries
- classpathEntries.add(JavaCore.newSourceEntry(libSrc.getFullPath()));
+ classpathEntries.add(JavaCore.newSourceEntry(libSrcPath));
+ } else {
+ // there's a valid match, do nothing, but remove the match from
+ // the list of previously existing CPE.
+ libCpeList.remove(match);
}
+ }
+ }
- // set the new list
- javaProject.setRawClasspath(
- classpathEntries.toArray(new IClasspathEntry[classpathEntries.size()]),
- monitor);
+ if (bundle.mCleanupCPE) {
+ // remove the remaining CPE as they could not be resolved.
+ classpathEntries.removeAll(libCpeList);
+ }
- for (IResource res : toDelete) {
- res.delete(true, monitor);
- }
+ // set the new list
+ javaProject.setRawClasspath(
+ classpathEntries.toArray(new IClasspathEntry[classpathEntries.size()]),
+ monitor);
- return Status.OK_STATUS;
- } catch (CoreException e) {
- AdtPlugin.logAndPrintError(e, "Library Project", "Failed to create link between library %1$s and project %2$s: %3$s",
- libraryState.getProjectState().getProject().getName(),
- projectState.getProject().getName(),
- e.getMessage());
- return e.getStatus();
+ if (bundle.mCleanupCPE) {
+ // and delete the folders of the CPE that were removed (must be done after)
+ for (IClasspathEntry cpe : libCpeList) {
+ IResource res = wsRoot.findMember(cpe.getPath());
+ res.delete(true, monitor);
}
}
- };
- if (doInJob) {
- Job job = new Job("Android Library link creation") { //$NON-NLS-1$
- @Override
- protected IStatus run(IProgressMonitor monitor) {
- return jobRunnable.run(monitor);
- }
- };
- job.setPriority(Job.BUILD);
- job.schedule();
- } else {
- jobRunnable.run(new NullProgressMonitor());
+ return Status.OK_STATUS;
+ } catch (CoreException e) {
+ AdtPlugin.logAndPrintError(e, bundle.mProject.getName(),
+ "Failed to create library links: %1$s", //$NON-NLS-1$
+ e.getMessage());
+ return e.getStatus();
+ }
+ }
+
+ /**
+ * Returns a {@link IClasspathEntry} from the given list whose linked path match the given path.
+ * @param cpeList a list of {@link IClasspathEntry} of {@link IClasspathEntry#getEntryKind()}
+ * {@link IClasspathEntry#CPE_SOURCE} whose {@link IClasspathEntry#getPath()}
+ * points to a linked folder.
+ * @param rawPath the raw path to compare to. Can be null if <var>path</var> is used instead.
+ * @param path the path to compare to. Can be null if <var>rawPath</var> is used instead.
+ * @return the matching IClasspathEntry or null.
+ */
+ private IClasspathEntry findClasspathEntryMatch(ArrayList<IClasspathEntry> cpeList,
+ IPath rawPath, IPath path) {
+ IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
+ for (IClasspathEntry cpe : cpeList) {
+ IPath cpePath = cpe.getPath();
+ // test the normal path of the resource.
+ if (path != null && path.equals(cpePath)) {
+ return cpe;
+ }
+
+ IResource res = wsRoot.findMember(cpePath);
+ // getRawLocation returns the path that the linked folder points to.
+ if (rawPath != null && res.getRawLocation().equals(rawPath)) {
+ return cpe;
+ }
+
}
+ return null;
}
/**
@@ -1177,138 +1439,176 @@ public final class Sdk {
* <p/>This can be done in a job in case the workspace is not locked for resource
* modification. See <var>doInJob</var>.
*
- * @param projectState the {@link ProjectState} for the main project
- * @param libraryProject the library project that needs to be removed
- * @param doInJob whether the action must be done in a new {@link Job}.
+ * @param bundle The {@link UnlinkLibraryBundle} action bundle that contains all the parameters
+ * necessary to execute the action.
+ * @param monitor an {@link IProgressMonitor}.
+ * @return an {@link IStatus} with the status of the action.
*/
- private void unlinkLibrary(final ProjectState projectState, final IProject libraryProject,
- boolean doInJob) {
- final IJobRunnable jobRunnable = new IJobRunnable() {
- public IStatus run(IProgressMonitor monitor) {
- try {
- IProject project = projectState.getProject();
-
- // if the library and the main project are closed at the same time, this
- // is likely to return false since this is run in a new job.
- if (project.isOpen() == false) {
- // cannot change the description of closed projects.
- return Status.OK_STATUS;
- }
+ private IStatus unlinkLibrary(UnlinkLibraryBundle bundle, IProgressMonitor monitor) {
+ try {
+ IProject project = bundle.mProject.getProject();
- // remove the library to the list of dynamic references
- IProjectDescription projectDescription = project.getDescription();
- IProject[] refs = projectDescription.getDynamicReferences();
-
- if (refs.length > 0) {
- ArrayList<IProject> list = new ArrayList<IProject>(Arrays.asList(refs));
-
- // remove a previous library if needed (in case of a rename)
- final int count = list.size();
- for (int i = 0 ; i < count ; i++) {
- // since project basically have only one segment that matter,
- // just check the names
- if (list.get(i).equals(libraryProject)) {
- list.remove(i);
- break;
- }
- }
+ // if the library and the main project are closed at the same time, this
+ // is likely to return false since this is run in a new job.
+ if (project.isOpen() == false) {
+ // cannot change the description of closed projects.
+ return Status.OK_STATUS;
+ }
- // set the changed list
- projectDescription.setDynamicReferences(
- list.toArray(new IProject[list.size()]));
+ // remove the library to the list of dynamic references
+ IProjectDescription projectDescription = project.getDescription();
+ IProject[] refs = projectDescription.getDynamicReferences();
+
+ if (refs.length > 0) {
+ ArrayList<IProject> list = new ArrayList<IProject>(Arrays.asList(refs));
+
+ // remove a previous library if needed (in case of a rename)
+ final int count = list.size();
+ for (int i = 0 ; i < count ; i++) {
+ // since project basically have only one segment that matter,
+ // just check the names
+ if (list.get(i).equals(bundle.mLibrary)) {
+ list.remove(i);
+ break;
}
+ }
- // edit the list of source folders.
- IJavaProject javaProject = JavaCore.create(project);
- IClasspathEntry[] entries = javaProject.getRawClasspath();
- ArrayList<IClasspathEntry> classpathEntries = new ArrayList<IClasspathEntry>(
- Arrays.asList(entries));
-
- IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
-
- // list to hold the source folder to delete, as they can't be delete before
- // they have been removed from the classpath entries
- ArrayList<IResource> toDelete = new ArrayList<IResource>();
-
- for (int i = 0 ; i < classpathEntries.size();) {
- IClasspathEntry classpathEntry = classpathEntries.get(i);
- if (classpathEntry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
- IPath path = classpathEntry.getPath();
- IResource linkedRes = wsRoot.findMember(path);
- if (linkedRes != null && linkedRes.isLinked() && CREATOR_ADT.equals(
- ProjectHelper.loadStringProperty(linkedRes, PROP_CREATOR))) {
- IResource originalLibrary = ProjectHelper.loadResourceProperty(
- linkedRes, PROP_LIBRARY);
-
- // if the library is missing, or if the library is the one being
- // unlinked:
- // remove the classpath entry and delete the linked folder.
- if (originalLibrary == null ||
- originalLibrary.equals(libraryProject)) {
- classpathEntries.remove(i);
- toDelete.add(linkedRes);
- continue; // don't increment i
- }
- }
- }
+ // set the changed list
+ projectDescription.setDynamicReferences(
+ list.toArray(new IProject[list.size()]));
+ }
- i++;
+ // edit the list of source folders.
+ IJavaProject javaProject = JavaCore.create(project);
+ IClasspathEntry[] entries = javaProject.getRawClasspath();
+ ArrayList<IClasspathEntry> classpathEntries = new ArrayList<IClasspathEntry>(
+ Arrays.asList(entries));
+
+ IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
+
+ // list to hold the source folder to delete, as they can't be delete before
+ // they have been removed from the classpath entries
+ ArrayList<IResource> toDelete = new ArrayList<IResource>();
+
+ for (int i = 0 ; i < classpathEntries.size();) {
+ IClasspathEntry classpathEntry = classpathEntries.get(i);
+ if (classpathEntry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
+ IPath path = classpathEntry.getPath();
+ IResource linkedRes = wsRoot.findMember(path);
+ if (linkedRes != null && linkedRes.isLinked() && CREATOR_ADT.equals(
+ ProjectHelper.loadStringProperty(linkedRes, PROP_CREATOR))) {
+ IResource originalLibrary = ProjectHelper.loadResourceProperty(
+ linkedRes, PROP_LIBRARY);
+
+ // if the library is missing, or if the library is the one being
+ // unlinked:
+ // remove the classpath entry and delete the linked folder.
+ if (originalLibrary == null ||
+ originalLibrary.equals(bundle.mLibrary)) {
+ classpathEntries.remove(i);
+ toDelete.add(linkedRes);
+ continue; // don't increment i
+ }
}
+ }
- // set the new list
- javaProject.setRawClasspath(
- classpathEntries.toArray(new IClasspathEntry[classpathEntries.size()]),
- monitor);
+ i++;
+ }
- // delete the resources that need deleting
- for (IResource res : toDelete) {
- res.delete(true, monitor);
- }
+ // set the new list
+ javaProject.setRawClasspath(
+ classpathEntries.toArray(new IClasspathEntry[classpathEntries.size()]),
+ monitor);
- return Status.OK_STATUS;
- } catch (CoreException e) {
- AdtPlugin.log(e, "Failure to unlink %1$s from %2$s", libraryProject.getName(),
- projectState.getProject().getName());
- return e.getStatus();
- }
+ // delete the resources that need deleting
+ for (IResource res : toDelete) {
+ res.delete(true, monitor);
}
- };
-
- if (doInJob) {
- Job job = new Job("Android Library unlinking") { //$NON-NLS-1$
- @Override
- protected IStatus run(IProgressMonitor monitor) {
- return jobRunnable.run(monitor);
- }
- };
- job.setPriority(Job.BUILD);
- job.schedule();
- } else {
- jobRunnable.run(new NullProgressMonitor());
+ return Status.OK_STATUS;
+ } catch (CoreException e) {
+ AdtPlugin.log(e, "Failure to unlink %1$s from %2$s", //$NON-NLS-1$
+ bundle.mLibrary.getName(),
+ bundle.mProject.getProject().getName());
+ return e.getStatus();
}
}
/**
- * Triggers a project recompilation in a new {@link Job}. This is useful when the
- * tree is locked and the {@link IProject#build(int, IProgressMonitor)} call would failed.
- * @param project the project to recompile.
+ * Updates all existing projects with a given list of new/updated libraries.
+ * This loops through all opened projects and check if they depend on any of the given
+ * library project, and if they do, they are linked together.
+ * @param libraries the list of new/updated library projects.
*/
- private void recompile(final IProject project) {
- Job job = new Job("Project recompilation trigger") { //$NON-NLS-1$
- @Override
- protected IStatus run(IProgressMonitor monitor) {
- try {
- project.build( IncrementalProjectBuilder.FULL_BUILD, null);
- return Status.OK_STATUS;
- } catch (CoreException e) {
- return e.getStatus();
+ private void updateProjectsWithNewLibraries(List<ProjectState> libraries) {
+ if (libraries.size() == 0) {
+ return;
+ }
+
+ ArrayList<ProjectState> updatedLibraries = new ArrayList<ProjectState>();
+ synchronized (sLock) {
+ // for each projects, look for projects that depend on it, and update them.
+ // Once they are updated (meaning ProjectState#needs() has been called on them),
+ // we add them to the list so that can be updated as well.
+ for (ProjectState projectState : sProjectStateMap.values()) {
+ // list for all the new library dependencies we find.
+ ArrayList<IProject> libsToLink = new ArrayList<IProject>();
+
+ for (ProjectState library : libraries) {
+ // Normally we would only need to test if ProjectState#needs returns non null,
+ // meaning the link between the project and the library has not been
+ // done yet.
+ // However what matters here is that the library is a dependency,
+ // period. If the library project was updated, then we redo the link,
+ // with all indirect dependencies (which *have* changed, since this is
+ // what this method is all about.)
+ // We still need to call ProjectState#needs to make the link in case it's not
+ // been done yet (which can happen if the library project was just opened).
+ if (projectState != library) {
+ LibraryState libState = projectState.needs(library);
+ if (libState != null || projectState.dependsOn(library)) {
+ // we have a match. Add it.
+ IProject libProject = library.getProject();
+
+ // library could already be here if it was an indirect dependencies
+ // from a previously processed updated library.
+ if (libsToLink.contains(libProject) == false) {
+ libsToLink.add(libProject);
+ }
+
+ // now find what this depends on, and add it too.
+ // The order here doesn't matter
+ // as it's just to add the linked source folder, so there's no
+ // need to use ProjectState#getFullLibraryProjects() which
+ // could return project that have already been added anyway.
+ fillProjectDependenciesList(library, libsToLink);
+ }
+ }
+ }
+
+ if (libsToLink.size() > 0) {
+ // create an action bundle for this link
+ LinkLibraryBundle bundle = new LinkLibraryBundle();
+ bundle.mProject = projectState.getProject();
+ bundle.mLibraryProjects = libsToLink.toArray(
+ new IProject[libsToLink.size()]);
+ bundle.mPreviousLibraryPath = null;
+ bundle.mCleanupCPE = false;
+ startActionBundle(bundle);
+
+ // if this updated project is a library, add it to the list, so that
+ // projects depending on it get updated too.
+ if (projectState.isLibrary() &&
+ updatedLibraries.contains(projectState) == false) {
+ updatedLibraries.add(projectState);
+ }
}
}
- };
+ }
- job.setPriority(Job.BUILD);
- job.schedule();
+ // done, but there may be updated projects that were libraries, so we need to do the same
+ // for this libraries, to update the project there were depending on.
+ updateProjectsWithNewLibraries(updatedLibraries);
}
/**