Index: plugin.xml =================================================================== --- plugin.xml (revision 608) +++ plugin.xml (working copy) @@ -156,6 +156,12 @@ class="org.jayasoft.ivyde.eclipse.ui.preferences.IvyPreferencePage" id="org.jayasoft.ivyde.eclipse.ui.preferences.IvyPreferencePage"> + + Index: src/java/org/jayasoft/ivyde/eclipse/cpcontainer/IvyClasspathContainer.java =================================================================== --- src/java/org/jayasoft/ivyde/eclipse/cpcontainer/IvyClasspathContainer.java (revision 608) +++ src/java/org/jayasoft/ivyde/eclipse/cpcontainer/IvyClasspathContainer.java (working copy) @@ -135,13 +135,15 @@ } protected IStatus run(IProgressMonitor monitor) { + IvyPlugin.ensureConsoleInitialized(); Message.info("resolving dependencies of "+_ivyXmlFile); _monitor = monitor; final IStatus[] status = new IStatus[1]; - final ClasspathItem[][] classpathItems = new ClasspathItem[1][]; + final IClasspathEntry[][] classpathEntries = new IClasspathEntry[1][]; Thread resolver = new Thread() { public void run() { + // Just launched new thread - need to re-acquire context IvyPlugin.setIvyContext(_javaProject); _ivy.addIvyListener(IvyResolveJob.this); @@ -216,13 +218,13 @@ if (!resolved) { Message.info("\n\nIVY DE: using cached data of previous resolve of "+md.getModuleRevisionId().getModuleId()+"\n"); } - classpathItems[0] = parseResolvedConfs(confs, mid); + classpathEntries[0] = parseResolvedConfs(confs, mid); } catch (Exception ex) { if (!resolved) { //maybe this is a problem with the cache, we retry with an actual resolve Message.info("\n\nIVYDE: tryed to build classpath from cache, but files seemed to be corrupted... trying with an actual resolve"); ResolveReport report = _ivy.resolve(ivyURL, null, _confs, _ivy.getDefaultCache(), null, true); - classpathItems[0] = parseResolvedConfs(report.getConfigurations(), mid); + classpathEntries[0] = parseResolvedConfs(report.getConfigurations(), mid); } } } catch (Exception e) { @@ -268,7 +270,7 @@ } } if (status[0] == Status.OK_STATUS) { - updateClasspathEntries(_usePreviousResolveIfExist, _notify, classpathItems[0]); + updateClasspathEntries(_usePreviousResolveIfExist, _notify, classpathEntries[0]); } return status[0]; } finally { @@ -281,33 +283,42 @@ } - private ClasspathItem[] parseResolvedConfs(String[] confs, ModuleId mid) throws ParseException, IOException { - ClasspathItem[] classpathItems; + private IClasspathEntry[] parseResolvedConfs(String[] confs, ModuleId mid) throws ParseException, IOException { + IClasspathEntry[] classpathEntries; XmlReportParser parser = new XmlReportParser(); Collection all = new LinkedHashSet(); for (int i = 0; i < confs.length; i++) { Artifact[] artifacts = parser.getArtifacts(mid, confs[i], _ivy.getDefaultCache()); all.addAll(Arrays.asList(artifacts)); } - Collection files = new LinkedHashSet(); + Collection paths = new LinkedHashSet(); for (Iterator iter = all.iterator(); iter.hasNext();) { Artifact artifact = (Artifact)iter.next(); - if (IvyPlugin.accept(_javaProject, artifact)) { - File sourcesArtifact = getSourcesArtifact(artifact, all); - File javadocArtifact = getJavadocArtifact(artifact, all); - files.add(new ClasspathItem( - _ivy.getArchiveFileInCache(_ivy.getDefaultCache(), artifact), - sourcesArtifact, - javadocArtifact - )); + + if (artifact.getType().equals("project")) { + // This is a java project in the workspace, add project path + paths.add(JavaCore.newProjectEntry(new Path(artifact.getName()), true)); + } else if (IvyPlugin.accept(_javaProject, artifact)) { + + Path classpathArtifact = new Path(_ivy.getArchiveFileInCache(_ivy.getDefaultCache(), artifact).getAbsolutePath()); + Path sourcesArtifact = getSourcesArtifactPath(artifact, all); + Path javadocArtifact = getJavadocArtifactPath(artifact, all); + + paths.add(JavaCore.newLibraryEntry(classpathArtifact, + getSourceAttachment(classpathArtifact, sourcesArtifact), + getSourceAttachmentRoot(classpathArtifact, sourcesArtifact), + ClasspathEntry.NO_ACCESS_RULES, + getExtraAttribute(classpathArtifact, javadocArtifact), + false)); + } } - classpathItems = (ClasspathItem[])files.toArray(new ClasspathItem[files.size()]); + classpathEntries = (IClasspathEntry[])paths.toArray(new IClasspathEntry[paths.size()]); - return classpathItems; + return classpathEntries; } - - private File getSourcesArtifact(Artifact artifact, Collection all) + + private Path getSourcesArtifactPath(Artifact artifact, Collection all) { for (Iterator iter = all.iterator(); iter.hasNext();) { Artifact a = (Artifact)iter.next(); @@ -315,13 +326,13 @@ a.getId().getRevision().equals(artifact.getId().getRevision()) && IvyPlugin.isSources(_javaProject, a)) { - return _ivy.getArchiveFileInCache(_ivy.getDefaultCache(), a); + return new Path(_ivy.getArchiveFileInCache(_ivy.getDefaultCache(), a).getAbsolutePath()); } } return null; } - private File getJavadocArtifact(Artifact artifact, Collection all) + private Path getJavadocArtifactPath(Artifact artifact, Collection all) { for (Iterator iter = all.iterator(); iter.hasNext();) { Artifact a = (Artifact)iter.next(); @@ -330,39 +341,42 @@ a.getId().equals(artifact.getId()) && IvyPlugin.isJavadoc(_javaProject, a)) { - return _ivy.getArchiveFileInCache(_ivy.getDefaultCache(), a); + return new Path(_ivy.getArchiveFileInCache(_ivy.getDefaultCache(), a).getAbsolutePath()); } } return null; } - } - - public class ClasspathItem - { - private File classpathArtifact; - private File sourcesArtifact; - private File javadocArtifact; - - public ClasspathItem(File classpathArtifact, File sourcesArtifact, File javadocArtifact) - { - this.classpathArtifact = classpathArtifact; - this.sourcesArtifact = sourcesArtifact; - this.javadocArtifact = javadocArtifact; - } - - public Path getClasspathArtifactPath() { - return new Path(classpathArtifact.getAbsolutePath()); - } - - public Path getSourcesArtifactPath() { - return (sourcesArtifact != null) ? new Path(sourcesArtifact.getAbsolutePath()) : null; - } - public Path getJavadocArtifactPath() { - return (javadocArtifact != null) ? new Path(javadocArtifact.getAbsolutePath()) : null; - } - } + private IPath getSourceAttachment(Path classpathArtifact, Path sourcesArtifact) { + IPath sourceAttachment = IvyPlugin.getDefault().getPackageFragmentExtraInfo().getSourceAttachment(classpathArtifact); + if (sourceAttachment == null) + sourceAttachment = sourcesArtifact; + + return sourceAttachment; + } + + private IPath getSourceAttachmentRoot(Path classpathArtifact, Path sourcesArtifact) { + IPath sourceAttachment = IvyPlugin.getDefault().getPackageFragmentExtraInfo().getSourceAttachmentRoot(classpathArtifact); + if (sourceAttachment == null && sourcesArtifact != null) + sourceAttachment = sourcesArtifact; + + return sourceAttachment; + } + private IClasspathAttribute[] getExtraAttribute(Path classpathArtifact, Path javadocArtifact) { + List result = new ArrayList(); + IPath p = IvyPlugin.getDefault().getPackageFragmentExtraInfo().getDocAttachment(classpathArtifact); + + if (p == null) + p = javadocArtifact; + + if(p != null) { + result.add(new ClasspathAttribute(IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME, p.toPortableString())); + } + return (IClasspathAttribute[]) result.toArray(new IClasspathAttribute[result.size()]); + } + } + public static final String IVY_CLASSPATH_CONTAINER_ID = "org.jayasoft.ivyde.eclipse.cpcontainer.IVYDE_CONTAINER"; private IClasspathEntry[] _classpathEntries; @@ -393,6 +407,16 @@ IvyPlugin.getDefault().register(this); } + private IvyClasspathContainer(IvyClasspathContainer clone) { + _javaProject = clone._javaProject; + _path = clone._path; + + _ivyXmlPath = clone._ivyXmlPath; + _ivyXmlFile = clone._ivyXmlFile; + _confs = clone._confs; + _classpathEntries = clone._classpathEntries; + } + private File resolveFile( String path ) { IFile iFile = _javaProject.getProject().getFile( path ); return new File( iFile.getLocation().toOSString() ); @@ -455,8 +479,7 @@ return _job; } } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); + IvyPlugin.log(IStatus.ERROR, "Errors occurred trying to run Ivy resolve job", e); return null; } } @@ -469,20 +492,9 @@ } - private void updateClasspathEntries(final boolean usePreviousResolveIfExist, boolean notify, final ClasspathItem[] classpathItems) { - if(classpathItems != null) { - IClasspathEntry[] entries = new IClasspathEntry[ classpathItems.length ]; - - for( int i=0; iHow it works + * During a resolve, it looks at all open projects in the workspace that have + * Ivy containers. + * The first project that publishes the module + * on which the project being resolved depends, will be picked and + * returned as a special type of artifact called "project". + * + * The IvyClasspathContainer will recognize the artifact as a project and put the + * eclipse project as a dependent project within the classpath container of the parent. + * + * If you do not want a project to be linked as a dependency, close it or delete from the workspace. + * As soon as you do that, any projects that were linked to it will automatically re-resolve + * (see {@link WorkspaceResourceChangeListener}) + * and use the standard Ivy means of finding the dependency. + * + * The {@link WorkspaceResourceChangeListener} will also auto-resolve when a new + * project is added or opened, so opening a project will automatically link it into + * the currently open projects where necessary. + * + * Since the resolver is not aware which module revision a project + * is publishing, it optimistically matches any revision of the module. + * + * Since the resolver stops after finding the first open project which matches the module, + * having multiple open versions of the same project in the workspace + * (for example, different branches) may set the wrong version as a dependency. + * You are advised to only open the version of the project which you + * want other projects in the workspace to depend on. + * + * NOTE: Transitive dependencies are not passed from the dependent project + * to the parent when projects are linked. If you find you are missing some + * transitive dependencies, just set your dependent eclipse project to export + * its ivy dependencies. + * (Project->Properties->Java Build Path->Order and Export-> + * -> check the ivy container) + * This will only export the configuration that project is using and not + * what a dependent project may ask for when it's being resolved. + * To do that, this resolver will need to be modified to pass transitive + * dependencies along. + + * How to set it up + * Set up a custom resolver in the resolve chain (see example below), + * + * Usage Example: + * + * Suppose you have a main ivyconf.xml which sets up all your resolvers to be + * executed at command-line. + * + * 1) Create a new file which includes your main ivyconf.xml file + * 2) Define the custom eclipse resolver type. + * 3) Create a simple chain which uses the eclipse resolver first and then + * the resolver which would normally be used in your config file. + * It could be the default resolver or any other, depending on your situation. + * 4) (Optional) To optimize, if you know you will only be developing modules + * in your organization, you may limit the calls to the eclipse resolver + * to only modules in your organization, by using the module directives. + * + * IMPORTANT NOTE: If the main config file already restricts modules in your organization + * to a specific resolver, you will not be able to override it in your own file. + * Either completely duplicate the main file and add eclipse resolver there, + * or have a base file without any org-specific restrictions and then two + * files which include it: the eclipse config and the main config. + * + * (See Ivy manual for your reference). + * 5) In Eclipse preferences, navigate to Ivy preferences and select the + * new config file just created as your default configuration file. + * + * Sample ivyconf-eclipse.xml: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * @author Eugene Goldfarb + * + */ +public class WorkspaceResolver extends AbstractResolver { + + /* + * (non-Javadoc) + * @see fr.jayasoft.ivy.DependencyResolver#download(fr.jayasoft.ivy.Artifact[], fr.jayasoft.ivy.Ivy, java.io.File, boolean) + */ + public DownloadReport download(Artifact[] artifacts, Ivy ivy, File cache, boolean useOrigin) { + + // Not much to do here - downloads are not required for workspace projects. + + DownloadReport dr = new DownloadReport(); + for (int i = 0; i < artifacts.length; i++) { + final ArtifactDownloadReport adr = new ArtifactDownloadReport(artifacts[i]); + dr.addArtifactReport(adr); + + // Only report java projects as downloaded + if (artifacts[i].getType().equals("project")) { + Message.verbose("\t[WORKSPACE ] "+artifacts[i]); + adr.setDownloadStatus(DownloadStatus.NO); + adr.setSize(0); + } else { + Message.verbose("\t[Eclipse Workspace resolver - skipping non-project artifact] "+artifacts[i]); + adr.setDownloadStatus(DownloadStatus.NO); + } + } + return dr; + } + + /* + * (non-Javadoc) + * @see fr.jayasoft.ivy.DependencyResolver#getDependency(fr.jayasoft.ivy.DependencyDescriptor, fr.jayasoft.ivy.ResolveData) + */ + public ResolvedModuleRevision getDependency(DependencyDescriptor dd, + ResolveData data) throws ParseException { + + ModuleId mid = dd.getDependencyId(); + + try { + IJavaProject[] projects = JavaModelManager.getJavaModelManager().getJavaModel().getJavaProjects(); + + // Iterate over workspace to find Java project which has an Ivy + // container for this dependency + for (int i = 0; i < projects.length; i++) { + IJavaProject javaProject = projects[i]; + IClasspathEntry[] entries = javaProject.getRawClasspath(); + for (int j= 0; j < entries.length; j++) { + IClasspathEntry entry= entries[j]; + + ModuleDescriptor md = findModuleDescriptor(javaProject, entry); + + if (md != null) { + // Found one; check if it is for the module we need + if (mid.equals(md.getModuleRevisionId().getModuleId())) { + + // Get a revision which will match desired revision + ModuleRevisionId localId = generateLocalId(dd.getDependencyRevisionId()); + Artifact af = new DefaultArtifact( + localId, + md.getPublicationDate(), + javaProject.getPath().toString(), + "project", + "project"); + + DefaultModuleDescriptor workspaceMd = + DefaultModuleDescriptor.newDefaultInstance(localId); + + // Find the configuration for which the resolve is running, and find out what configuration of + // the dependency is required. We will always try to accomodate the resolver and supply + // the artifacts for every configuration +// String[] allConfs = dd.getDependencyConfigurations(data.getReport().getConfiguration()); + + // In Ivy 1.4.1, Ivy behavior changed since 1.3 or 1.4 and this stopped working. Before, it called resolve many times + // when different configurations were needed. Now, it looks like it's been + // optimized and calls resolve once. + // So, the resolver is supposed to return all possible configurations + // in case they are needed later, and not what is asked for. + String[] allConfs = md.getConfigurationsNames(); + if (allConfs.length == 0) { + workspaceMd.addArtifact(ModuleDescriptor.DEFAULT_CONFIGURATION, af); + } else { + for (int k=0; kEugene Goldfarb + * + */ +public class WorkspaceResourceChangeListener implements IResourceChangeListener { + + public void resourceChanged(IResourceChangeEvent event) { + + try { + if (event.getType() == IResourceChangeEvent.PRE_CLOSE + || event.getType() == IResourceChangeEvent.PRE_DELETE) { + + if (IvyPlugin.getDefault().getPreferenceStore().getBoolean(PreferenceConstants.AUTO_RESOLVE_ON_CLOSE)) { + + // Check if one of Ivy projects is being removed + final IResource res = event.getResource(); + final IJavaProject javaProject = JavaModelManager.getJavaModelManager().getJavaModel().getJavaProject(res); + if (javaProject != null) { + IPath path = null; + IClasspathEntry[] entries = javaProject.getRawClasspath(); + for (int i= 0; i < entries.length; i++) { + IClasspathEntry entry= entries[i]; + if (entry != null && entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER && + IvyClasspathContainer.isIvyClasspathContainer(entry.getPath())) { + path = entry.getPath(); + break; + } + } + + if (path != null) { + // Found an Ivy container in this project -- notify dependent projects to perform fresh resolve + + // Let's try to be nice and use the workspace method + // to schedule resolves in dependent projects after the close operation + // has finished. + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + IResourceRuleFactory ruleFactory = workspace.getRuleFactory(); + ISchedulingRule modifyRule = ruleFactory.modifyRule(res); + class IvyClosedProjectJob extends WorkspaceJob { + + public IvyClosedProjectJob() { + super("IvyClosedProjectJob"); + } + public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException { + resolveAffectedProjects(javaProject.getPath()); + return Status.OK_STATUS; + } + }; + IvyClosedProjectJob job = new IvyClosedProjectJob(); + job.setRule(modifyRule); + job.schedule(); + + } + } + } + } else if (event.getType() == IResourceChangeEvent.POST_CHANGE) { + + if (IvyPlugin.getDefault().getPreferenceStore().getBoolean(PreferenceConstants.AUTO_RESOLVE_ON_OPEN)) { + + /* FIXME: This is not working yet, it triggers too many events + // Find out if a project was opened. + IResourceDelta delta = event.getDelta(); + + if (delta != null) { + final IResourceDelta[] projDeltas = delta.getAffectedChildren(IResourceDelta.CHANGED); + String deltaString = ((ResourceDelta)delta).toDeepDebugString(); + if (projDeltas.length > 0) { + final Collection projects = new LinkedHashSet(projDeltas.length); + for (int i =0; i < projDeltas.length; i++) { + if ((projDeltas[i].getFlags() & IResourceDelta.OPEN) != 0) { + IResourceDelta rootDelta = projDeltas[i]; + IResourceDelta[] childDeltas = rootDelta.getAffectedChildren(); + if (childDeltas.length>0 && (childDeltas[0].getFlags() & IResourceDelta.ADDED) != 0) + projects.add(projDeltas[i].getResource()); + } + } + + if (projects.size() > 0) { + // Found an Ivy container in this project -- notify dependent projects to perform fresh resolve + // Let's try to be nice and use the workspace method + // to schedule resolves in dependent projects after the close operation + // has finished. + class IvyOpenProjectJob extends WorkspaceJob { + + public IvyOpenProjectJob() { + super("IvyOpenProjectJob"); + } + public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException { + resolveAllProjectsExcept(projects); + return Status.OK_STATUS; + } + }; + IvyOpenProjectJob job = new IvyOpenProjectJob(); + job.schedule(); + } + } + } + */ + } + } + } catch (JavaModelException jme) { + IvyPlugin.log(IStatus.ERROR, "Errors occurred trying to find projects affected by closure", jme); + } catch (OperationCanceledException oce) { + IvyPlugin.log(IStatus.CANCEL, "Ivy update of dependent proejects affected by project close operation canceled", null); + } catch (CoreException ce) { + IvyPlugin.log(IStatus.ERROR, "Errors occurred trying to find projects affected by closure", ce); + + } + } + + /* + * Only resolve those projects which include the specified project path as ivy dependency + */ + private void resolveAffectedProjects (IPath projectPath) { + + try { + IWorkspaceRoot root= ResourcesPlugin.getWorkspace().getRoot(); + IJavaProject[] projects= JavaCore.create(root).getJavaProjects(); + + for (int i= 0; i < projects.length; i++) { + IJavaProject javaProject= projects[i]; + IClasspathEntry[] entries= javaProject.getRawClasspath(); + for (int k= 0; k < entries.length; k++) { + IClasspathEntry entry= entries[k]; + if (entry != null && entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) { + IPath path = entry.getPath(); + if (IvyClasspathContainer.isIvyClasspathContainer(path)) { + IClasspathContainer cp = JavaCore.getClasspathContainer(path, javaProject); + if (cp instanceof IvyClasspathContainer) { + IvyClasspathContainer c = (IvyClasspathContainer)cp; + IClasspathEntry[] containerEntries = c.getClasspathEntries(); + for (int j=0; j < containerEntries.length; j++) { + IClasspathEntry containerEntry = containerEntries[j]; + if (containerEntry!=null && + containerEntry.getEntryKind() == IClasspathEntry.CPE_PROJECT && + containerEntry.getPath().equals(projectPath)) { + c.resolve(); + break; + } + } + } + } + } + } + } + } catch (JavaModelException e) { + IvyPlugin.log(IStatus.ERROR, "Errors occurred trying to find dependent projects", e); + } + } + + private void resolveAllProjectsExcept (Collection sourceProjects) { + try { + IJavaProject[] projects = JavaModelManager.getJavaModelManager().getJavaModel().getJavaProjects(); + for (int i = 0; i < projects.length; i++) { + if (!sourceProjects.contains(projects[i])) { + IvyClasspathContainer.resolve(projects[i]); + } + } + } catch (JavaModelException e) { + IvyPlugin.log(IStatus.ERROR, "Errors occurred trying to resolve all projects", e); + } + } + +} Property changes on: src\java\org\jayasoft\ivyde\eclipse\resolver\WorkspaceResourceChangeListener.java ___________________________________________________________________ Name: svn:mime-type + text/plain Name: svn:keywords + Author Date Revision Id URL Name: svn:eol-style + native Index: src/java/org/jayasoft/ivyde/eclipse/resolver/WorkspaceResolver.java =================================================================== --- src/java/org/jayasoft/ivyde/eclipse/resolver/WorkspaceResolver.java (revision 0) +++ src/java/org/jayasoft/ivyde/eclipse/resolver/WorkspaceResolver.java (revision 0) @@ -0,0 +1,304 @@ +package org.jayasoft.ivyde.eclipse.resolver; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.jdt.core.IClasspathContainer; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.internal.core.JavaModelManager; +import org.jayasoft.ivyde.eclipse.IvyPlugin; +import org.jayasoft.ivyde.eclipse.cpcontainer.IvyClasspathContainer; + +import fr.jayasoft.ivy.Artifact; +import fr.jayasoft.ivy.Configuration; +import fr.jayasoft.ivy.DefaultArtifact; +import fr.jayasoft.ivy.DefaultModuleDescriptor; +import fr.jayasoft.ivy.DefaultModuleRevision; +import fr.jayasoft.ivy.DependencyDescriptor; +import fr.jayasoft.ivy.Ivy; +import fr.jayasoft.ivy.ModuleDescriptor; +import fr.jayasoft.ivy.ModuleId; +import fr.jayasoft.ivy.ModuleRevisionId; +import fr.jayasoft.ivy.ResolveData; +import fr.jayasoft.ivy.ResolvedModuleRevision; +import fr.jayasoft.ivy.parser.ModuleDescriptorParserRegistry; +import fr.jayasoft.ivy.report.ArtifactDownloadReport; +import fr.jayasoft.ivy.report.DownloadReport; +import fr.jayasoft.ivy.report.DownloadStatus; +import fr.jayasoft.ivy.resolver.AbstractResolver; +import fr.jayasoft.ivy.util.Message; + +/** + * This is an Eclipse workspace Ivy resolver. + * When used with the custom IvyClasspathContainer changes, + * this resolver will link dependent projects when they are open in the same workspace, + * allowing full-fledged linked project functionality Eclipse provides, such as incremental + * compilation, debugging, mouseover javadocs, and source browsing across projects. + * + * The resolver will not work until it is properly configured, see instructions below. + * + * How it works + * During a resolve, it looks at all open projects in the workspace that have + * Ivy containers. + * The first project that publishes the module + * on which the project being resolved depends, will be picked and + * returned as a special type of artifact called "project". + * + * The IvyClasspathContainer will recognize the artifact as a project and put the + * eclipse project as a dependent project within the classpath container of the parent. + * + * If you do not want a project to be linked as a dependency, close it or delete from the workspace. + * As soon as you do that, any projects that were linked to it will automatically re-resolve + * (see {@link WorkspaceResourceChangeListener}) + * and use the standard Ivy means of finding the dependency. + * + * The {@link WorkspaceResourceChangeListener} will also auto-resolve when a new + * project is added or opened, so opening a project will automatically link it into + * the currently open projects where necessary. + * + * Since the resolver is not aware which module revision a project + * is publishing, it optimistically matches any revision of the module. + * + * Since the resolver stops after finding the first open project which matches the module, + * having multiple open versions of the same project in the workspace + * (for example, different branches) may set the wrong version as a dependency. + * You are advised to only open the version of the project which you + * want other projects in the workspace to depend on. + * + * NOTE: Transitive dependencies are not passed from the dependent project + * to the parent when projects are linked. If you find you are missing some + * transitive dependencies, just set your dependent eclipse project to export + * its ivy dependencies. + * (Project->Properties->Java Build Path->Order and Export-> + * -> check the ivy container) + * This will only export the configuration that project is using and not + * what a dependent project may ask for when it's being resolved. + * To do that, this resolver will need to be modified to pass transitive + * dependencies along. + + * How to set it up + * Set up a custom resolver in the resolve chain (see example below), + * + * Usage Example: + * + * Suppose you have a main ivyconf.xml which sets up all your resolvers to be + * executed at command-line. + * + * 1) Create a new file which includes your main ivyconf.xml file + * 2) Define the custom eclipse resolver type. + * 3) Create a simple chain which uses the eclipse resolver first and then + * the resolver which would normally be used in your config file. + * It could be the default resolver or any other, depending on your situation. + * 4) (Optional) To optimize, if you know you will only be developing modules + * in your organization, you may limit the calls to the eclipse resolver + * to only modules in your organization, by using the module directives. + * + * IMPORTANT NOTE: If the main config file already restricts modules in your organization + * to a specific resolver, you will not be able to override it in your own file. + * Either completely duplicate the main file and add eclipse resolver there, + * or have a base file without any org-specific restrictions and then two + * files which include it: the eclipse config and the main config. + * + * (See Ivy manual for your reference). + * 5) In Eclipse preferences, navigate to Ivy preferences and select the + * new config file just created as your default configuration file. + * + * Sample ivyconf-eclipse.xml: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * @author Eugene Goldfarb + * + */ +public class WorkspaceResolver extends AbstractResolver { + + /* + * (non-Javadoc) + * @see fr.jayasoft.ivy.DependencyResolver#download(fr.jayasoft.ivy.Artifact[], fr.jayasoft.ivy.Ivy, java.io.File, boolean) + */ + public DownloadReport download(Artifact[] artifacts, Ivy ivy, File cache, boolean useOrigin) { + + // Not much to do here - downloads are not required for workspace projects. + + DownloadReport dr = new DownloadReport(); + for (int i = 0; i < artifacts.length; i++) { + final ArtifactDownloadReport adr = new ArtifactDownloadReport(artifacts[i]); + dr.addArtifactReport(adr); + + // Only report java projects as downloaded + if (artifacts[i].getType().equals("project")) { + Message.verbose("\t[WORKSPACE ] "+artifacts[i]); + adr.setDownloadStatus(DownloadStatus.NO); + adr.setSize(0); + } else { + Message.verbose("\t[Eclipse Workspace resolver - skipping non-project artifact] "+artifacts[i]); + adr.setDownloadStatus(DownloadStatus.NO); + } + } + return dr; + } + + /* + * (non-Javadoc) + * @see fr.jayasoft.ivy.DependencyResolver#getDependency(fr.jayasoft.ivy.DependencyDescriptor, fr.jayasoft.ivy.ResolveData) + */ + public ResolvedModuleRevision getDependency(DependencyDescriptor dd, + ResolveData data) throws ParseException { + + ModuleId mid = dd.getDependencyId(); + + try { + IJavaProject[] projects = JavaModelManager.getJavaModelManager().getJavaModel().getJavaProjects(); + + // Iterate over workspace to find Java project which has an Ivy + // container for this dependency + for (int i = 0; i < projects.length; i++) { + IJavaProject javaProject = projects[i]; + IClasspathEntry[] entries = javaProject.getRawClasspath(); + for (int j= 0; j < entries.length; j++) { + IClasspathEntry entry= entries[j]; + + ModuleDescriptor md = findModuleDescriptor(javaProject, entry); + + if (md != null) { + // Found one; check if it is for the module we need + if (mid.equals(md.getModuleRevisionId().getModuleId())) { + + // Get a revision which will match desired revision + ModuleRevisionId localId = generateLocalId(dd.getDependencyRevisionId()); + Artifact af = new DefaultArtifact( + localId, + md.getPublicationDate(), + javaProject.getPath().toString(), + "project", + "project"); + + DefaultModuleDescriptor workspaceMd = + DefaultModuleDescriptor.newDefaultInstance(localId); + + // Find the configuration for which the resolve is running, and find out what configuration of + // the dependency is required. We will always try to accomodate the resolver and supply + // the artifacts for every configuration +// String[] allConfs = dd.getDependencyConfigurations(data.getReport().getConfiguration()); + + // In Ivy 1.4.1, Ivy behavior changed since 1.3 or 1.4 and this stopped working. Before, it called resolve many times + // when different configurations were needed. Now, it looks like it's been + // optimized and calls resolve once. + // So, the resolver is supposed to return all possible configurations + // in case they are needed later, and not what is asked for. + String[] allConfs = md.getConfigurationsNames(); + if (allConfs.length == 0) { + workspaceMd.addArtifact(ModuleDescriptor.DEFAULT_CONFIGURATION, af); + } else { + for (int k=0; kEugene Goldfarb + * + */ +public class WorkspaceResourceChangeListener implements IResourceChangeListener { + + public void resourceChanged(IResourceChangeEvent event) { + + try { + if (event.getType() == IResourceChangeEvent.PRE_CLOSE + || event.getType() == IResourceChangeEvent.PRE_DELETE) { + + if (IvyPlugin.getDefault().getPreferenceStore().getBoolean(PreferenceConstants.AUTO_RESOLVE_ON_CLOSE)) { + + // Check if one of Ivy projects is being removed + final IResource res = event.getResource(); + final IJavaProject javaProject = JavaModelManager.getJavaModelManager().getJavaModel().getJavaProject(res); + if (javaProject != null) { + IPath path = null; + IClasspathEntry[] entries = javaProject.getRawClasspath(); + for (int i= 0; i < entries.length; i++) { + IClasspathEntry entry= entries[i]; + if (entry != null && entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER && + IvyClasspathContainer.isIvyClasspathContainer(entry.getPath())) { + path = entry.getPath(); + break; + } + } + + if (path != null) { + // Found an Ivy container in this project -- notify dependent projects to perform fresh resolve + + // Let's try to be nice and use the workspace method + // to schedule resolves in dependent projects after the close operation + // has finished. + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + IResourceRuleFactory ruleFactory = workspace.getRuleFactory(); + ISchedulingRule modifyRule = ruleFactory.modifyRule(res); + class IvyClosedProjectJob extends WorkspaceJob { + + public IvyClosedProjectJob() { + super("IvyClosedProjectJob"); + } + public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException { + resolveAffectedProjects(javaProject.getPath()); + return Status.OK_STATUS; + } + }; + IvyClosedProjectJob job = new IvyClosedProjectJob(); + job.setRule(modifyRule); + job.schedule(); + + } + } + } + } else if (event.getType() == IResourceChangeEvent.POST_CHANGE) { + + if (IvyPlugin.getDefault().getPreferenceStore().getBoolean(PreferenceConstants.AUTO_RESOLVE_ON_OPEN)) { + + /* FIXME: This is not working yet, it triggers too many events + // Find out if a project was opened. + IResourceDelta delta = event.getDelta(); + + if (delta != null) { + final IResourceDelta[] projDeltas = delta.getAffectedChildren(IResourceDelta.CHANGED); + String deltaString = ((ResourceDelta)delta).toDeepDebugString(); + if (projDeltas.length > 0) { + final Collection projects = new LinkedHashSet(projDeltas.length); + for (int i =0; i < projDeltas.length; i++) { + if ((projDeltas[i].getFlags() & IResourceDelta.OPEN) != 0) { + IResourceDelta rootDelta = projDeltas[i]; + IResourceDelta[] childDeltas = rootDelta.getAffectedChildren(); + if (childDeltas.length>0 && (childDeltas[0].getFlags() & IResourceDelta.ADDED) != 0) + projects.add(projDeltas[i].getResource()); + } + } + + if (projects.size() > 0) { + // Found an Ivy container in this project -- notify dependent projects to perform fresh resolve + // Let's try to be nice and use the workspace method + // to schedule resolves in dependent projects after the close operation + // has finished. + class IvyOpenProjectJob extends WorkspaceJob { + + public IvyOpenProjectJob() { + super("IvyOpenProjectJob"); + } + public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException { + resolveAllProjectsExcept(projects); + return Status.OK_STATUS; + } + }; + IvyOpenProjectJob job = new IvyOpenProjectJob(); + job.schedule(); + } + } + } + */ + } + } + } catch (JavaModelException jme) { + IvyPlugin.log(IStatus.ERROR, "Errors occurred trying to find projects affected by closure", jme); + } catch (OperationCanceledException oce) { + IvyPlugin.log(IStatus.CANCEL, "Ivy update of dependent proejects affected by project close operation canceled", null); + } catch (CoreException ce) { + IvyPlugin.log(IStatus.ERROR, "Errors occurred trying to find projects affected by closure", ce); + + } + } + + /* + * Only resolve those projects which include the specified project path as ivy dependency + */ + private void resolveAffectedProjects (IPath projectPath) { + + try { + IWorkspaceRoot root= ResourcesPlugin.getWorkspace().getRoot(); + IJavaProject[] projects= JavaCore.create(root).getJavaProjects(); + + for (int i= 0; i < projects.length; i++) { + IJavaProject javaProject= projects[i]; + IClasspathEntry[] entries= javaProject.getRawClasspath(); + for (int k= 0; k < entries.length; k++) { + IClasspathEntry entry= entries[k]; + if (entry != null && entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) { + IPath path = entry.getPath(); + if (IvyClasspathContainer.isIvyClasspathContainer(path)) { + IClasspathContainer cp = JavaCore.getClasspathContainer(path, javaProject); + if (cp instanceof IvyClasspathContainer) { + IvyClasspathContainer c = (IvyClasspathContainer)cp; + IClasspathEntry[] containerEntries = c.getClasspathEntries(); + for (int j=0; j < containerEntries.length; j++) { + IClasspathEntry containerEntry = containerEntries[j]; + if (containerEntry!=null && + containerEntry.getEntryKind() == IClasspathEntry.CPE_PROJECT && + containerEntry.getPath().equals(projectPath)) { + c.resolve(); + break; + } + } + } + } + } + } + } + } catch (JavaModelException e) { + IvyPlugin.log(IStatus.ERROR, "Errors occurred trying to find dependent projects", e); + } + } + + private void resolveAllProjectsExcept (Collection sourceProjects) { + try { + IJavaProject[] projects = JavaModelManager.getJavaModelManager().getJavaModel().getJavaProjects(); + for (int i = 0; i < projects.length; i++) { + if (!sourceProjects.contains(projects[i])) { + IvyClasspathContainer.resolve(projects[i]); + } + } + } catch (JavaModelException e) { + IvyPlugin.log(IStatus.ERROR, "Errors occurred trying to resolve all projects", e); + } + } + +} Property changes on: src\java\org\jayasoft\ivyde\eclipse\resolver\WorkspaceResourceChangeListener.java ___________________________________________________________________ Name: svn:mime-type + text/plain Name: svn:keywords + Author Date Revision Id URL Name: svn:eol-style + native Index: src/java/org/jayasoft/ivyde/eclipse/ui/preferences/PreferenceConstants.java =================================================================== --- src/java/org/jayasoft/ivyde/eclipse/ui/preferences/PreferenceConstants.java (revision 608) +++ src/java/org/jayasoft/ivyde/eclipse/ui/preferences/PreferenceConstants.java (working copy) @@ -20,5 +20,8 @@ public static final String P_STRING = "stringPreference"; public static final String DO_RETRIEVE = "do.retreive"; public static final String RETRIEVE_PATTERN = "retreive.pattern"; - + + public static final String AUTO_RESOLVE_ON_OPEN = "autoResolve.open"; + public static final String AUTO_RESOLVE_ON_CLOSE = "autoResolve.close"; + } Index: src/java/org/jayasoft/ivyde/eclipse/ui/preferences/WorkspaceResolverPreferencePage.java =================================================================== --- src/java/org/jayasoft/ivyde/eclipse/ui/preferences/WorkspaceResolverPreferencePage.java (revision 0) +++ src/java/org/jayasoft/ivyde/eclipse/ui/preferences/WorkspaceResolverPreferencePage.java (revision 0) @@ -0,0 +1,88 @@ +package org.jayasoft.ivyde.eclipse.ui.preferences; + +import java.io.File; +import java.net.MalformedURLException; +import org.eclipse.jface.preference.BooleanFieldEditor; +import org.eclipse.jface.preference.FieldEditorPreferencePage; +import org.eclipse.jface.preference.FileFieldEditor; +import org.eclipse.jface.preference.StringFieldEditor; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; +import org.jayasoft.ivyde.eclipse.IvyPlugin; + + +/** + * This class represents a preference page that + * is contributed to the Preferences dialog. By + * subclassing FieldEditorPreferencePage, we + * can use the field support built into JFace that allows + * us to create a page that is small and knows how to + * save, restore and apply itself. + *

+ * This page is used to modify preferences only. They + * are stored in the preference store that belongs to + * the main plug-in class. That way, preferences can + * be accessed directly via the preference store. + */ + +public class WorkspaceResolverPreferencePage + extends FieldEditorPreferencePage + implements IWorkbenchPreferencePage { + + public WorkspaceResolverPreferencePage() { + super(GRID); + setPreferenceStore(IvyPlugin.getDefault().getPreferenceStore()); + setDescription(""); + } + + /** + * Creates the field editors. Field editors are abstractions of + * the common GUI blocks needed to manipulate various types + * of preferences. Each field editor knows how to save and + * restore itself. + */ + public void createFieldEditors() { + final Composite fieldParent = getFieldEditorParent(); + + Label spacer = new Label(fieldParent, SWT.NONE); + GridData spacerData = new GridData(); + spacerData.horizontalSpan = 3; + spacer.setLayoutData(spacerData); + spacer.setText("Workspace Resolver"); + spacer = new Label(fieldParent, SWT.SEPARATOR | SWT.HORIZONTAL ); + spacer.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, true, false, 3, 1)); + + BooleanFieldEditor aro = new BooleanFieldEditor(PreferenceConstants.AUTO_RESOLVE_ON_OPEN, "Auto resolve on project open", fieldParent); + aro.setEnabled(false, fieldParent); // TODO: enable when it works + addField(aro); + + new Label(fieldParent, SWT.NONE); // space + Label explanation = new Label(fieldParent, SWT.NONE); + explanation.setLayoutData(new GridData(GridData.FILL, GridData.BEGINNING, false, false, 2, 1)); + explanation.setText("Will automatically resolve projects in the workspace and link open project where necessary"); + new Label(fieldParent, SWT.NONE).setLayoutData(new GridData(GridData.FILL, GridData.BEGINNING, false, false, 3, 1)); // space + + addField(new BooleanFieldEditor(PreferenceConstants.AUTO_RESOLVE_ON_CLOSE, "Auto resolve on project close", fieldParent)); + + explanation = new Label(fieldParent, SWT.NONE); + explanation.setLayoutData(new GridData(GridData.FILL, GridData.BEGINNING, false, false, 2, 1)); + explanation.setText("Will automatically resolve projects in the workspace after dependent project is closed"); + new Label(fieldParent, SWT.NONE).setLayoutData(new GridData(GridData.FILL, GridData.BEGINNING, false, false, 3, 1)); // space + + } + + + /* (non-Javadoc) + * @see org.eclipse.ui.IWorkbenchPreferencePage#init(org.eclipse.ui.IWorkbench) + */ + public void init(IWorkbench workbench) { + } + +} \ No newline at end of file Property changes on: src\java\org\jayasoft\ivyde\eclipse\ui\preferences\WorkspaceResolverPreferencePage.java ___________________________________________________________________ Name: svn:mime-type + text/plain Name: svn:keywords + Author Date Revision Id URL Name: svn:eol-style + native