A workspace resolver for Ant.
The code is inspired from IvyDE’s one. Work in progress.
diff --git a/src/java/org/apache/ivy/ant/AntWorkspaceResolver.java b/src/java/org/apache/ivy/ant/AntWorkspaceResolver.java
new file mode 100644
index 0000000..27cf686
--- /dev/null
+++ b/src/java/org/apache/ivy/ant/AntWorkspaceResolver.java
@@ -0,0 +1,231 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.DownloadReport;
+import org.apache.ivy.core.report.DownloadStatus;
+import org.apache.ivy.core.resolve.DownloadOptions;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParserRegistry;
+import org.apache.ivy.plugins.resolver.AbstractWorkspaceResolver;
+import org.apache.ivy.util.Message;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.types.DataType;
+import org.apache.tools.ant.types.ResourceCollection;
+import org.apache.tools.ant.types.resources.FileResource;
+
+public class AntWorkspaceResolver extends DataType {
+
+ public static final class WorkspaceArtifact {
+
+ private String name;
+
+ private String type;
+
+ private String ext;
+
+ private String path;
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public void setExt(String ext) {
+ this.ext = ext;
+ }
+
+ public void setPath(String path) {
+ this.path = path;
+ }
+ }
+
+ private List<ResourceCollection> allResources = new ArrayList<ResourceCollection>();
+
+ private boolean haltOnError = true;
+
+ private Resolver resolver;
+
+ private String name;
+
+ private List<WorkspaceArtifact> artifacts = new ArrayList<WorkspaceArtifact>();
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setHaltonerror(boolean haltOnError) {
+ this.haltOnError = haltOnError;
+ }
+
+ public void addResourceCollection(ResourceCollection resources) {
+ if (!resources.isFilesystemOnly()) {
+ throw new BuildException("Only filesystem resource collection is supported");
+ }
+ allResources.add(resources);
+ }
+
+ public WorkspaceArtifact createArtifact() {
+ WorkspaceArtifact a = new WorkspaceArtifact();
+ artifacts.add(a);
+ return a;
+ }
+
+ public Resolver getResolver() {
+ if (resolver == null) {
+ if (name == null) {
+ throw new BuildException("A name is required");
+ }
+ resolver = new Resolver();
+ resolver.setName(name);
+ }
+ return resolver;
+ }
+
+ private String getProjectName(File ivyFile) {
+ return ivyFile.getParentFile().getName();
+ }
+
+ private class Resolver extends AbstractWorkspaceResolver {
+
+ private Map<ModuleDescriptor, File> md2IvyFile;
+
+ private synchronized Map<ModuleDescriptor, File> getModuleDescriptors() {
+ if (md2IvyFile == null) {
+ md2IvyFile = new HashMap<ModuleDescriptor, File>();
+ for (ResourceCollection resources : allResources) {
+ for (Iterator it = resources.iterator(); it.hasNext();) {
+ File ivyFile = ((FileResource) it.next()).getFile();
+ try {
+ ModuleDescriptor md = ModuleDescriptorParserRegistry.getInstance()
+ .parseDescriptor(getParserSettings(), ivyFile.toURI().toURL(),
+ isValidate());
+ md2IvyFile.put(md, ivyFile);
+ Message.debug("Add " + md.getModuleRevisionId().getModuleId());
+ } catch (Exception ex) {
+ if (haltOnError) {
+ throw new BuildException("impossible to parse ivy file " + ivyFile
+ + " exception=" + ex, ex);
+ } else {
+ Message.warn("impossible to parse ivy file " + ivyFile
+ + " exception=" + ex.getMessage());
+ }
+ }
+ }
+ }
+ }
+ return md2IvyFile;
+ }
+
+ public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data)
+ throws ParseException {
+ Map<ModuleDescriptor, File> mds = getModuleDescriptors();
+ for (Entry<ModuleDescriptor, File> md : mds.entrySet()) {
+ ResolvedModuleRevision rmr = checkCandidate(dd, md.getKey(),
+ getProjectName(md.getValue()));
+ if (rmr != null) {
+ return rmr;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected List<Artifact> createWorkspaceArtifacts(ModuleDescriptor md) {
+ List<Artifact> res = new ArrayList<Artifact>();
+
+ for (WorkspaceArtifact wa : artifacts) {
+ String name = wa.name;
+ String type = wa.type;
+ String ext = wa.ext;
+ String path = wa.path;
+ if (name == null) {
+ name = md.getModuleRevisionId().getName();
+ }
+ if (type == null) {
+ type = "jar";
+ }
+ if (ext == null) {
+ ext = "jar";
+ }
+ if (path == null) {
+ path = "target" + File.separator + "dist" + File.separator + type + "s"
+ + File.separator + name + "." + ext;
+ }
+
+ URL url;
+ File ivyFile = md2IvyFile.get(md);
+ File artifactFile = new File(ivyFile.getParentFile(), path);
+ try {
+ url = artifactFile.toURI().toURL();
+ } catch (MalformedURLException e) {
+ throw new RuntimeException("Unsupported file path : " + artifactFile, e);
+ }
+
+ res.add(new DefaultArtifact(md.getModuleRevisionId(), new Date(), name, type, ext,
+ url, null));
+ }
+
+ return res;
+ }
+
+ public DownloadReport download(Artifact[] artifacts, DownloadOptions options) {
+ // Not much to do here - downloads are not required for workspace projects.
+ DownloadReport dr = new DownloadReport();
+ for (int i = 0; i < artifacts.length; i++) {
+ ArtifactDownloadReport adr = new ArtifactDownloadReport(artifacts[i]);
+ dr.addArtifactReport(adr);
+ adr.setDownloadStatus(DownloadStatus.NO);
+ adr.setSize(0);
+ URL url = artifacts[i].getUrl();
+ File f;
+ try {
+ f = new File(url.toURI());
+ } catch (URISyntaxException e) {
+ f = new File(url.getPath());
+ }
+ adr.setLocalFile(f);
+ Message.verbose("\t[IN WORKSPACE] " + artifacts[i]);
+ }
+ return dr;
+ }
+ }
+
+}
diff --git a/src/java/org/apache/ivy/ant/IvyAntSettings.java b/src/java/org/apache/ivy/ant/IvyAntSettings.java
index 660ebbe..bb67ba7 100644
--- a/src/java/org/apache/ivy/ant/IvyAntSettings.java
+++ b/src/java/org/apache/ivy/ant/IvyAntSettings.java
@@ -102,6 +102,8 @@
private boolean autoRegistered = false;
+ private AntWorkspaceResolver antWorkspaceResolver;
+
/**
* Returns the default ivy settings of this classloader. If it doesn't exist yet, a new one is
* created using the given project to back the VariableContainer.
@@ -275,6 +277,10 @@
defineDefaultSettingFile(ivyAntVariableContainer, task);
}
+ if (antWorkspaceResolver != null) {
+ settings.addConfigured(antWorkspaceResolver.getResolver());
+ }
+
Ivy ivy = Ivy.newInstance(settings);
try {
ivy.pushContext();
@@ -388,4 +394,7 @@
URLHandlerRegistry.setDefault(dispatcher);
}
+ public void addConfiguredWorkspaceResolver(AntWorkspaceResolver antWorkspaceResolver) {
+ this.antWorkspaceResolver = antWorkspaceResolver;
+ }
}
diff --git a/src/java/org/apache/ivy/ant/IvyConfigure.java b/src/java/org/apache/ivy/ant/IvyConfigure.java
index 5e05adc..6926d02 100644
--- a/src/java/org/apache/ivy/ant/IvyConfigure.java
+++ b/src/java/org/apache/ivy/ant/IvyConfigure.java
@@ -130,6 +130,11 @@
settings.setPasswd(passwd);
}
+ public void addConfiguredWorkspaceResolver(AntWorkspaceResolver resolver) {
+ settings.addConfiguredWorkspaceResolver(resolver);
+ }
+
+ @Override
public void execute() throws BuildException {
String settingsId = settings.getId();
Object otherRef = getProject().getReference(settingsId);
diff --git a/src/java/org/apache/ivy/ant/antlib.xml b/src/java/org/apache/ivy/ant/antlib.xml
index ac32740..0111f78 100644
--- a/src/java/org/apache/ivy/ant/antlib.xml
+++ b/src/java/org/apache/ivy/ant/antlib.xml
@@ -49,4 +49,5 @@
<taskdef name="fixdeps" classname="org.apache.ivy.ant.FixDepsTask" />
<taskdef name="dependencytree" classname="org.apache.ivy.ant.IvyDependencyTree"/>
<taskdef name="checkdepsupdate" classname="org.apache.ivy.ant.IvyDependencyUpdateChecker"/>
+ <typedef name="workspaceresolver" classname="org.apache.ivy.ant.AntWorkspaceResolver" />
</antlib>
diff --git a/src/java/org/apache/ivy/core/module/descriptor/DefaultWorkspaceModuleDescriptor.java b/src/java/org/apache/ivy/core/module/descriptor/DefaultWorkspaceModuleDescriptor.java
new file mode 100644
index 0000000..1c86f56
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/DefaultWorkspaceModuleDescriptor.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+import java.util.Date;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
+import org.apache.ivy.plugins.repository.Resource;
+
+public class DefaultWorkspaceModuleDescriptor extends DefaultModuleDescriptor implements
+ WorkspaceModuleDescriptor {
+
+ public DefaultWorkspaceModuleDescriptor(ModuleDescriptorParser parser, Resource res) {
+ super(parser, res);
+ }
+
+ public DefaultWorkspaceModuleDescriptor(ModuleRevisionId id, String status, Date pubDate) {
+ super(id, status, pubDate);
+ }
+
+ public DefaultWorkspaceModuleDescriptor(ModuleRevisionId id, String status, Date pubDate,
+ boolean isDefault) {
+ super(id, status, pubDate, isDefault);
+ }
+
+}
diff --git a/src/java/org/apache/ivy/core/module/descriptor/WorkspaceModuleDescriptor.java b/src/java/org/apache/ivy/core/module/descriptor/WorkspaceModuleDescriptor.java
new file mode 100644
index 0000000..4403526
--- /dev/null
+++ b/src/java/org/apache/ivy/core/module/descriptor/WorkspaceModuleDescriptor.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.ivy.core.module.descriptor;
+
+/**
+ * Marker interface for module descriptor of a module in a workspace, then having special semantics,
+ * like artifacts declared by the resolver rather than the descriptor
+ */
+public interface WorkspaceModuleDescriptor extends ModuleDescriptor {
+
+}
diff --git a/src/java/org/apache/ivy/core/resolve/IvyNodeUsage.java b/src/java/org/apache/ivy/core/resolve/IvyNodeUsage.java
index b6631f2..cb0eafe 100644
--- a/src/java/org/apache/ivy/core/resolve/IvyNodeUsage.java
+++ b/src/java/org/apache/ivy/core/resolve/IvyNodeUsage.java
@@ -27,6 +27,7 @@
import org.apache.ivy.core.module.descriptor.DependencyArtifactDescriptor;
import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
import org.apache.ivy.core.module.descriptor.IncludeRule;
+import org.apache.ivy.core.module.descriptor.WorkspaceModuleDescriptor;
/**
* Class collecting usage data for an IvyNode.
@@ -219,6 +220,10 @@
}
protected Set<DependencyArtifactDescriptor> getDependencyArtifactsSet(String rootModuleConf) {
+ if (node.getDescriptor() instanceof WorkspaceModuleDescriptor) {
+ // for a module in the "workspace" artifacts will be actually declared by the resolver
+ return null;
+ }
Collection<Depender> dependersInConf = dependers.get(rootModuleConf);
if (dependersInConf == null) {
return null;
diff --git a/src/java/org/apache/ivy/core/settings/IvySettings.java b/src/java/org/apache/ivy/core/settings/IvySettings.java
index de806bf..5113cf5 100644
--- a/src/java/org/apache/ivy/core/settings/IvySettings.java
+++ b/src/java/org/apache/ivy/core/settings/IvySettings.java
@@ -76,6 +76,7 @@
import org.apache.ivy.plugins.latest.LatestRevisionStrategy;
import org.apache.ivy.plugins.latest.LatestStrategy;
import org.apache.ivy.plugins.latest.LatestTimeStrategy;
+import org.apache.ivy.plugins.latest.WorkspaceLatestStrategy;
import org.apache.ivy.plugins.lock.CreateFileLockStrategy;
import org.apache.ivy.plugins.lock.LockStrategy;
import org.apache.ivy.plugins.lock.NIOFileLockStrategy;
@@ -92,10 +93,12 @@
import org.apache.ivy.plugins.report.LogReportOutputter;
import org.apache.ivy.plugins.report.ReportOutputter;
import org.apache.ivy.plugins.report.XmlReportOutputter;
+import org.apache.ivy.plugins.resolver.AbstractWorkspaceResolver;
import org.apache.ivy.plugins.resolver.ChainResolver;
import org.apache.ivy.plugins.resolver.DependencyResolver;
import org.apache.ivy.plugins.resolver.DualResolver;
import org.apache.ivy.plugins.resolver.ResolverSettings;
+import org.apache.ivy.plugins.resolver.WorkspaceChainResolver;
import org.apache.ivy.plugins.signer.SignatureGenerator;
import org.apache.ivy.plugins.trigger.Trigger;
import org.apache.ivy.plugins.version.ChainVersionMatcher;
@@ -207,6 +210,8 @@
private PackingRegistry packingRegistry = new PackingRegistry();
+ private AbstractWorkspaceResolver workspaceResolver;
+
public IvySettings() {
this(new IvyVariableContainerImpl());
}
@@ -879,9 +884,20 @@
dictatorResolver = resolver;
}
+ private DependencyResolver getDictatorResolver() {
+ if (dictatorResolver == null) {
+ return null;
+ }
+ if (workspaceResolver != null && !(dictatorResolver instanceof WorkspaceChainResolver)) {
+ dictatorResolver = new WorkspaceChainResolver(this, dictatorResolver, workspaceResolver);
+ }
+ return dictatorResolver;
+ }
+
public synchronized DependencyResolver getResolver(ModuleRevisionId mrid) {
- if (dictatorResolver != null) {
- return dictatorResolver;
+ DependencyResolver r = getDictatorResolver();
+ if (r != null) {
+ return r;
}
String resolverName = getResolverName(mrid);
return getResolver(resolverName);
@@ -892,23 +908,32 @@
}
public synchronized DependencyResolver getResolver(String resolverName) {
- if (dictatorResolver != null) {
- return dictatorResolver;
+ DependencyResolver r = getDictatorResolver();
+ if (r != null) {
+ return r;
}
DependencyResolver resolver = resolversMap.get(resolverName);
if (resolver == null) {
Message.error("unknown resolver " + resolverName);
}
+ if (workspaceResolver != null && !(resolver instanceof WorkspaceChainResolver)) {
+ resolver = new WorkspaceChainResolver(this, resolver, workspaceResolver);
+ resolversMap.put(resolverName, resolver);
+ }
return resolver;
}
public synchronized DependencyResolver getDefaultResolver() {
- if (dictatorResolver != null) {
- return dictatorResolver;
+ DependencyResolver r = getDictatorResolver();
+ if (r != null) {
+ return r;
}
if (defaultResolver == null) {
defaultResolver = resolversMap.get(defaultResolverName);
}
+ if (workspaceResolver != null && !(defaultResolver instanceof WorkspaceChainResolver)) {
+ defaultResolver = new WorkspaceChainResolver(this, defaultResolver, workspaceResolver);
+ }
return defaultResolver;
}
@@ -997,7 +1022,12 @@
if ("default".equals(name)) {
return getDefaultLatestStrategy();
}
- return latestStrategies.get(name);
+ LatestStrategy strategy = latestStrategies.get(name);
+ if (workspaceResolver != null && !(strategy instanceof WorkspaceLatestStrategy)) {
+ strategy = new WorkspaceLatestStrategy(strategy);
+ latestStrategies.put(name, strategy);
+ }
+ return strategy;
}
public synchronized void addLatestStrategy(String name, LatestStrategy latest) {
@@ -1224,6 +1254,10 @@
if (defaultLatestStrategy == null) {
defaultLatestStrategy = new LatestRevisionStrategy();
}
+ if (workspaceResolver != null
+ && !(defaultLatestStrategy instanceof WorkspaceLatestStrategy)) {
+ defaultLatestStrategy = new WorkspaceLatestStrategy(defaultLatestStrategy);
+ }
return defaultLatestStrategy;
}
@@ -1500,4 +1534,20 @@
public PackingRegistry getPackingRegistry() {
return packingRegistry;
}
+
+ public void addConfigured(AbstractWorkspaceResolver workspaceResolver) {
+ this.workspaceResolver = workspaceResolver;
+ if (workspaceResolver != null) {
+ workspaceResolver.setSettings(this);
+ DefaultRepositoryCacheManager cacheManager = new DefaultRepositoryCacheManager();
+ String cacheName = "workspace-resolver-cache-" + workspaceResolver.getName();
+ cacheManager.setBasedir(new File(getDefaultCache(), cacheName));
+ cacheManager.setCheckmodified(true);
+ cacheManager.setUseOrigin(true);
+ cacheManager.setName(cacheName);
+ addRepositoryCacheManager(cacheManager);
+ workspaceResolver.setCache(cacheName);
+ }
+
+ }
}
diff --git a/src/java/org/apache/ivy/plugins/latest/WorkspaceLatestStrategy.java b/src/java/org/apache/ivy/plugins/latest/WorkspaceLatestStrategy.java
new file mode 100644
index 0000000..64ef7ec
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/latest/WorkspaceLatestStrategy.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.latest;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A strategy which delegate to another strategy, unless for the latest and working revisions which
+ * are considered as superior to any other revision. < br/>
+ * NB : it is for internal usage of Ivy only!
+ */
+public class WorkspaceLatestStrategy extends AbstractLatestStrategy {
+
+ private LatestStrategy delegate;
+
+ public WorkspaceLatestStrategy(LatestStrategy delegate) {
+ this.delegate = delegate;
+ setName("workspace-" + delegate.getName());
+ }
+
+ public List<ArtifactInfo> sort(ArtifactInfo[] infos) {
+ List<ArtifactInfo> sorted = delegate.sort(infos);
+
+ List<ArtifactInfo> head = new ArrayList<ArtifactInfo>();
+ List<ArtifactInfo> tail = new ArrayList<ArtifactInfo>();
+
+ for (ArtifactInfo ai : sorted) {
+ String rev = ai.getRevision();
+ boolean latestRev = rev.startsWith("latest") || rev.startsWith("working");
+ if (latestRev) {
+ head.add(ai);
+ } else {
+ tail.add(ai);
+ }
+ }
+
+ head.addAll(tail);
+ return head;
+ }
+
+}
\ No newline at end of file
diff --git a/src/java/org/apache/ivy/plugins/resolver/AbstractWorkspaceResolver.java b/src/java/org/apache/ivy/plugins/resolver/AbstractWorkspaceResolver.java
new file mode 100644
index 0000000..5444ab0
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/AbstractWorkspaceResolver.java
@@ -0,0 +1,239 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.List;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.Configuration;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.descriptor.DefaultWorkspaceModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ExcludeRule;
+import org.apache.ivy.core.module.descriptor.License;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.WorkspaceModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.DownloadStatus;
+import org.apache.ivy.core.report.MetadataArtifactDownloadReport;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.osgi.core.BundleInfo;
+import org.apache.ivy.osgi.core.ManifestHeaderElement;
+import org.apache.ivy.osgi.core.ManifestHeaderValue;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.apache.ivy.plugins.version.VersionMatcher;
+import org.apache.ivy.util.Message;
+
+public abstract class AbstractWorkspaceResolver extends AbstractResolver {
+
+ private boolean ignoreBranch;
+
+ private boolean ignoreVersion;
+
+ public void setIgnoreBranch(boolean ignoreBranch) {
+ this.ignoreBranch = ignoreBranch;
+ }
+
+ public void setIgnoreVersion(boolean ignoreVersion) {
+ this.ignoreVersion = ignoreVersion;
+ }
+
+ protected ResolvedModuleRevision checkCandidate(DependencyDescriptor dd, ModuleDescriptor md,
+ String workspaceModuleName) {
+
+ if (workspaceModuleName == null) {
+ workspaceModuleName = dd.getDependencyId().toString();
+ }
+
+ ModuleRevisionId dependencyMrid = dd.getDependencyRevisionId();
+ String org = dependencyMrid.getModuleId().getOrganisation();
+ String module = dependencyMrid.getModuleId().getName();
+
+ VersionMatcher versionMatcher = getSettings().getVersionMatcher();
+
+ ModuleRevisionId candidateMrid = md.getModuleRevisionId();
+
+ // search a match on the organization and the module name
+
+ if (org.equals(BundleInfo.BUNDLE_TYPE)) {
+ // looking for an OSGi bundle via its symbolic name
+ String sn = md.getExtraInfoContentByTagName("Bundle-SymbolicName");
+ if (sn == null || !module.equals(sn)) {
+ // not found, skip to next
+ return null;
+ }
+ } else if (org.equals(BundleInfo.PACKAGE_TYPE)) {
+ // looking for an OSGi bundle via its exported package
+ String exportedPackages = md.getExtraInfoContentByTagName("Export-Package");
+ if (exportedPackages == null) {
+ // not found, skip to next
+ return null;
+ }
+ boolean found = false;
+ String version = null;
+ ManifestHeaderValue exportElements;
+ try {
+ exportElements = new ManifestHeaderValue(exportedPackages);
+ } catch (ParseException e) {
+ // wrong OSGi header: skip it
+ return null;
+ }
+ for (ManifestHeaderElement exportElement : exportElements.getElements()) {
+ if (exportElement.getValues().contains(module)) {
+ found = true;
+ version = exportElement.getAttributes().get("version");
+ break;
+ }
+ }
+ if (!found) {
+ // not found, skip to next
+ return null;
+ }
+ if (version == null) {
+ // no version means anything can match. Let's trick the version matcher by
+ // setting the exact expected version
+ version = dependencyMrid.getRevision();
+ }
+ md.setResolvedModuleRevisionId(ModuleRevisionId.newInstance(org, module, version));
+ } else {
+ if (!candidateMrid.getModuleId().equals(dependencyMrid.getModuleId())) {
+ // it doesn't match org#module, skip to next
+ return null;
+ }
+ }
+
+ Message.verbose("Workspace resolver found potential matching workspace module "
+ + workspaceModuleName + " with module " + candidateMrid + " for module "
+ + dependencyMrid);
+
+ if (!ignoreBranch) {
+ ModuleId mid = dependencyMrid.getModuleId();
+ String defaultBranch = getSettings().getDefaultBranch(mid);
+ String dependencyBranch = dependencyMrid.getBranch();
+ String candidateBranch = candidateMrid.getBranch();
+ if (dependencyBranch == null) {
+ dependencyBranch = defaultBranch;
+ }
+ if (candidateBranch == null) {
+ candidateBranch = defaultBranch;
+ }
+ if (dependencyBranch != candidateBranch) {
+ // Both cannot be null
+ if (dependencyBranch == null || candidateBranch == null) {
+ Message.verbose("\t\trejected since branches doesn't match (one is set, the other isn't)");
+ return null;
+ }
+ if (!dependencyBranch.equals(candidateBranch)) {
+ Message.verbose("\t\trejected since branches doesn't match");
+ return null;
+ }
+ }
+ }
+
+ // Found one; check if it is for the module we need
+ if (!ignoreVersion
+ && !md.getModuleRevisionId().getRevision().equals(Ivy.getWorkingRevision())
+ && !versionMatcher.accept(dd.getDependencyRevisionId(), md)) {
+ Message.verbose("\t\treject as version didn't match");
+ return null;
+ }
+
+ if (ignoreVersion) {
+ Message.verbose("\t\tmatched (version are ignored)");
+ } else {
+ Message.verbose("\t\tversion matched");
+ }
+
+ WorkspaceModuleDescriptor workspaceMd = createWorkspaceMd(md);
+
+ Artifact mdaf = md.getMetadataArtifact();
+ if (mdaf == null) {
+ mdaf = new DefaultArtifact(md.getModuleRevisionId(), md.getPublicationDate(),
+ workspaceModuleName, "ivy", "");
+ }
+ MetadataArtifactDownloadReport madr = new MetadataArtifactDownloadReport(mdaf);
+ madr.setDownloadStatus(DownloadStatus.SUCCESSFUL);
+ madr.setSearched(true);
+
+ return new ResolvedModuleRevision(this, this, workspaceMd, madr);
+ }
+
+ protected WorkspaceModuleDescriptor createWorkspaceMd(ModuleDescriptor md) {
+ DefaultWorkspaceModuleDescriptor newMd = new DefaultWorkspaceModuleDescriptor(
+ md.getModuleRevisionId(), "release", null, true);
+ newMd.addConfiguration(new Configuration(ModuleDescriptor.DEFAULT_CONFIGURATION));
+ newMd.setLastModified(System.currentTimeMillis());
+
+ newMd.setDescription(md.getDescription());
+ newMd.setHomePage(md.getHomePage());
+ newMd.setLastModified(md.getLastModified());
+ newMd.setPublicationDate(md.getPublicationDate());
+ newMd.setResolvedPublicationDate(md.getResolvedPublicationDate());
+ newMd.setStatus(md.getStatus());
+
+ List<Artifact> artifacts = createWorkspaceArtifacts(md);
+
+ Configuration[] allConfs = md.getConfigurations();
+ for (Artifact af : artifacts) {
+ if (allConfs.length == 0) {
+ newMd.addArtifact(ModuleDescriptor.DEFAULT_CONFIGURATION, af);
+ } else {
+ for (int k = 0; k < allConfs.length; k++) {
+ newMd.addConfiguration(allConfs[k]);
+ newMd.addArtifact(allConfs[k].getName(), af);
+ }
+ }
+ }
+
+ DependencyDescriptor[] dependencies = md.getDependencies();
+ for (int k = 0; k < dependencies.length; k++) {
+ newMd.addDependency(dependencies[k]);
+ }
+
+ ExcludeRule[] allExcludeRules = md.getAllExcludeRules();
+ for (int k = 0; k < allExcludeRules.length; k++) {
+ newMd.addExcludeRule(allExcludeRules[k]);
+ }
+
+ newMd.getExtraInfos().addAll(md.getExtraInfos());
+
+ License[] licenses = md.getLicenses();
+ for (int k = 0; k < licenses.length; k++) {
+ newMd.addLicense(licenses[k]);
+ }
+
+ return newMd;
+ }
+
+ abstract protected List<Artifact> createWorkspaceArtifacts(ModuleDescriptor md);
+
+ public void publish(Artifact artifact, File src, boolean overwrite) throws IOException {
+ throw new UnsupportedOperationException("publish not supported by " + getName());
+ }
+
+ public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
+ return null;
+ }
+
+}
diff --git a/src/java/org/apache/ivy/plugins/resolver/WorkspaceChainResolver.java b/src/java/org/apache/ivy/plugins/resolver/WorkspaceChainResolver.java
new file mode 100644
index 0000000..a3662cf
--- /dev/null
+++ b/src/java/org/apache/ivy/plugins/resolver/WorkspaceChainResolver.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.ivy.plugins.resolver;
+
+import org.apache.ivy.core.settings.IvySettings;
+
+/**
+ * Resolver which decorate normal resolver so that the workspace resolver can hijack the resolve
+ * process <br />
+ * NB : it is for internal usage of Ivy only!
+ */
+public class WorkspaceChainResolver extends ChainResolver {
+
+ public WorkspaceChainResolver(IvySettings settings, DependencyResolver delegate,
+ AbstractWorkspaceResolver workspaceResolver) {
+ setName("workspace-chain-" + delegate.getName());
+ setSettings(settings);
+ setReturnFirst(true);
+ add(workspaceResolver);
+ add(delegate);
+ }
+}
diff --git a/test/java/org/apache/ivy/ant/AntBuildResolverTest.java b/test/java/org/apache/ivy/ant/AntBuildResolverTest.java
new file mode 100644
index 0000000..6eb3c79
--- /dev/null
+++ b/test/java/org/apache/ivy/ant/AntBuildResolverTest.java
@@ -0,0 +1,213 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.ivy.ant;
+
+import java.io.File;
+
+import junit.framework.TestCase;
+
+import org.apache.ivy.ant.AntWorkspaceResolver.WorkspaceArtifact;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.DownloadStatus;
+import org.apache.ivy.core.report.ResolveReport;
+import org.apache.tools.ant.DefaultLogger;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.taskdefs.Delete;
+import org.apache.tools.ant.types.FileSet;
+import org.apache.tools.ant.types.Path;
+
+public class AntBuildResolverTest extends TestCase {
+
+ private static final ModuleRevisionId MRID_MODULE1 = ModuleRevisionId.newInstance("org.acme",
+ "module1", "1.1");
+
+ private static final ModuleRevisionId MRID_PROJECT1 = ModuleRevisionId.newInstance(
+ "org.apache.ivy.test", "project1", "0.1");
+
+ private File cache;
+
+ private Project project;
+
+ private IvyConfigure configure;
+
+ private WorkspaceArtifact wa;
+
+ @Override
+ protected void setUp() throws Exception {
+ createCache();
+ project = new Project();
+ DefaultLogger logger = new DefaultLogger();
+ logger.setMessageOutputLevel(Project.MSG_INFO);
+ logger.setOutputPrintStream(System.out);
+ logger.setErrorPrintStream(System.err);
+ project.addBuildListener(logger);
+ project.setProperty("ivy.cache.dir", cache.getAbsolutePath());
+
+ AntWorkspaceResolver antWorkspaceResolver = new AntWorkspaceResolver();
+ antWorkspaceResolver.setName("test-workspace");
+ wa = antWorkspaceResolver.createArtifact();
+ FileSet fileset = new FileSet();
+ fileset.setProject(project);
+ fileset.setDir(new File("test/workspace"));
+ fileset.setIncludes("*/ivy.xml");
+ antWorkspaceResolver.addResourceCollection(fileset);
+ antWorkspaceResolver.setProject(project);
+
+ configure = new IvyConfigure();
+ configure.setProject(project);
+ configure.setFile(new File("test/workspace/ivysettings.xml"));
+ configure.addConfiguredWorkspaceResolver(antWorkspaceResolver);
+ configure.execute();
+ }
+
+ private void createCache() {
+ cache = new File("build/cache");
+ cache.mkdirs();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ cleanCache();
+ }
+
+ private void cleanCache() {
+ Delete del = new Delete();
+ del.setProject(new Project());
+ del.setDir(cache);
+ del.execute();
+ }
+
+ public void testNoProject() throws Exception {
+ IvyResolve resolve = new IvyResolve();
+ resolve.setProject(project);
+ resolve.setFile(new File("test/workspace/project1/ivy.xml"));
+ resolve.setKeep(true);
+ resolve.execute();
+
+ ResolveReport report = (ResolveReport) project.getReference("ivy.resolved.report");
+ assertEquals(1, report.getDependencies().size());
+ assertEquals(MRID_MODULE1, report.getDependencies().get(0).getResolvedId());
+ }
+
+ public void testProject() throws Exception {
+ IvyResolve resolve = new IvyResolve();
+ resolve.setProject(project);
+ resolve.setFile(new File("test/workspace/project2/ivy.xml"));
+ resolve.setKeep(true);
+ resolve.execute();
+
+ ResolveReport report = (ResolveReport) project.getReference("ivy.resolved.report");
+ assertEquals(2, report.getDependencies().size());
+ assertEquals(MRID_PROJECT1, report.getDependencies().get(0).getResolvedId());
+ assertEquals(MRID_MODULE1, report.getDependencies().get(1).getResolvedId());
+ assertEquals(1, report.getArtifactsReports(MRID_PROJECT1).length);
+ assertEquals(DownloadStatus.NO,
+ report.getArtifactsReports(MRID_PROJECT1)[0].getDownloadStatus());
+ assertEquals(new File("test/workspace/project1/target/dist/jars/project1.jar").toURI()
+ .toURL(), report.getArtifactsReports(MRID_PROJECT1)[0].getArtifact().getUrl());
+ assertEquals(
+ new File("test/workspace/project1/target/dist/jars/project1.jar").getAbsoluteFile(),
+ report.getArtifactsReports(MRID_PROJECT1)[0].getLocalFile());
+ }
+
+ public void testProjectFolder() throws Exception {
+ wa.setPath("target/classes");
+
+ IvyResolve resolve = new IvyResolve();
+ resolve.setProject(project);
+ resolve.setFile(new File("test/workspace/project2/ivy.xml"));
+ resolve.setKeep(true);
+ resolve.execute();
+
+ ResolveReport report = (ResolveReport) project.getReference("ivy.resolved.report");
+ assertEquals(2, report.getDependencies().size());
+ assertEquals(MRID_PROJECT1, report.getDependencies().get(0).getResolvedId());
+ assertEquals(MRID_MODULE1, report.getDependencies().get(1).getResolvedId());
+ assertEquals(1, report.getArtifactsReports(MRID_PROJECT1).length);
+ assertEquals(DownloadStatus.NO,
+ report.getArtifactsReports(MRID_PROJECT1)[0].getDownloadStatus());
+ assertEquals(new File("test/workspace/project1/target/classes").toURI().toURL(),
+ report.getArtifactsReports(MRID_PROJECT1)[0].getArtifact().getUrl());
+ assertEquals(new File("test/workspace/project1/target/classes").getAbsoluteFile(),
+ report.getArtifactsReports(MRID_PROJECT1)[0].getLocalFile());
+ }
+
+ public void testDependencyArtifact() throws Exception {
+ IvyResolve resolve = new IvyResolve();
+ resolve.setProject(project);
+ resolve.setFile(new File("test/workspace/project3/ivy.xml"));
+ resolve.setKeep(true);
+ resolve.execute();
+
+ ResolveReport report = (ResolveReport) project.getReference("ivy.resolved.report");
+ assertEquals(2, report.getDependencies().size());
+ assertEquals(MRID_PROJECT1, report.getDependencies().get(0).getResolvedId());
+ assertEquals(MRID_MODULE1, report.getDependencies().get(1).getResolvedId());
+ assertEquals(1, report.getArtifactsReports(MRID_PROJECT1).length);
+ assertEquals(DownloadStatus.NO,
+ report.getArtifactsReports(MRID_PROJECT1)[0].getDownloadStatus());
+ assertEquals(new File("test/workspace/project1/target/dist/jars/project1.jar").toURI()
+ .toURL(), report.getArtifactsReports(MRID_PROJECT1)[0].getArtifact().getUrl());
+ assertEquals(
+ new File("test/workspace/project1/target/dist/jars/project1.jar").getAbsoluteFile(),
+ report.getArtifactsReports(MRID_PROJECT1)[0].getLocalFile());
+ }
+
+ public void testCachePath() throws Exception {
+ IvyResolve resolve = new IvyResolve();
+ resolve.setProject(project);
+ resolve.setFile(new File("test/workspace/project2/ivy.xml"));
+ resolve.setKeep(true);
+ resolve.execute();
+
+ IvyCachePath cachePath = new IvyCachePath();
+ cachePath.setProject(project);
+ cachePath.setPathid("test.cachepath.id");
+ cachePath.execute();
+
+ Path path = (Path) project.getReference("test.cachepath.id");
+ assertEquals(2, path.size());
+ assertEquals(
+ new File("test/workspace/project1/target/dist/jars/project1.jar").getAbsolutePath(),
+ path.list()[0]);
+ assertEquals(new File(cache, "org.acme/module1/jars/module1-1.1.jar").getAbsolutePath(),
+ path.list()[1]);
+ }
+
+ public void testCachePathFolder() throws Exception {
+ wa.setPath("target/classes");
+
+ IvyResolve resolve = new IvyResolve();
+ resolve.setProject(project);
+ resolve.setFile(new File("test/workspace/project2/ivy.xml"));
+ resolve.setKeep(true);
+ resolve.execute();
+
+ IvyCachePath cachePath = new IvyCachePath();
+ cachePath.setProject(project);
+ cachePath.setPathid("test.cachepath.id");
+ cachePath.execute();
+
+ Path path = (Path) project.getReference("test.cachepath.id");
+ assertEquals(2, path.size());
+ assertEquals(new File("test/workspace/project1/target/classes").getAbsolutePath(),
+ path.list()[0]);
+ assertEquals(new File(cache, "org.acme/module1/jars/module1-1.1.jar").getAbsolutePath(),
+ path.list()[1]);
+ }
+}
diff --git a/test/workspace/ivysettings.xml b/test/workspace/ivysettings.xml
new file mode 100644
index 0000000..26ebeaa
--- /dev/null
+++ b/test/workspace/ivysettings.xml
@@ -0,0 +1,30 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<ivysettings>
+
+ <settings defaultResolver="repo" />
+
+ <resolvers>
+ <filesystem name="repo">
+ <ivy pattern="${ivy.settings.dir}/repo/[organisation]/[module]/[revision]/[artifact].[ext]" />
+ <artifact pattern="${ivy.settings.dir}/repo/[organisation]/[module]/[revision]/[artifact].[ext]" />
+ </filesystem>
+ </resolvers>
+
+</ivysettings>
\ No newline at end of file
diff --git a/test/workspace/project1/ivy.xml b/test/workspace/project1/ivy.xml
new file mode 100644
index 0000000..bc8cc54
--- /dev/null
+++ b/test/workspace/project1/ivy.xml
@@ -0,0 +1,24 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<ivy-module version="2.0">
+ <info organisation="org.apache.ivy.test" module="project1" revision="0.1" />
+ <dependencies>
+ <dependency org="org.acme" name="module1" rev="1.1" />
+ </dependencies>
+</ivy-module>
\ No newline at end of file
diff --git a/test/workspace/project2/ivy.xml b/test/workspace/project2/ivy.xml
new file mode 100644
index 0000000..d995e6d
--- /dev/null
+++ b/test/workspace/project2/ivy.xml
@@ -0,0 +1,24 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<ivy-module version="2.0">
+ <info organisation="org.apache.ivy.test" module="project2" />
+ <dependencies>
+ <dependency org="org.apache.ivy.test" name="project1" rev="latest.integration" />
+ </dependencies>
+</ivy-module>
\ No newline at end of file
diff --git a/test/workspace/project3/ivy.xml b/test/workspace/project3/ivy.xml
new file mode 100644
index 0000000..82441dd
--- /dev/null
+++ b/test/workspace/project3/ivy.xml
@@ -0,0 +1,26 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<ivy-module version="2.0">
+ <info organisation="org.apache.ivy.test" module="project3" />
+ <dependencies>
+ <dependency org="org.apache.ivy.test" name="project1" rev="latest.integration">
+ <artifact name="test" type="jar" ext="jar" />
+ </dependency>
+ </dependencies>
+</ivy-module>
\ No newline at end of file
diff --git a/test/workspace/repo/org.acme/module1/1.1/ivy.xml b/test/workspace/repo/org.acme/module1/1.1/ivy.xml
new file mode 100644
index 0000000..754cc1f
--- /dev/null
+++ b/test/workspace/repo/org.acme/module1/1.1/ivy.xml
@@ -0,0 +1,21 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<ivy-module version="2.0">
+ <info organisation="org.acme" module="module1" revision="1.1" />
+</ivy-module>
\ No newline at end of file
diff --git a/test/workspace/repo/org.acme/module1/1.1/module1.jar b/test/workspace/repo/org.acme/module1/1.1/module1.jar
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/workspace/repo/org.acme/module1/1.1/module1.jar
diff --git a/test/workspace/repo/org.acme/module2/1.2/ivy.xml b/test/workspace/repo/org.acme/module2/1.2/ivy.xml
new file mode 100644
index 0000000..990acfb
--- /dev/null
+++ b/test/workspace/repo/org.acme/module2/1.2/ivy.xml
@@ -0,0 +1,21 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<ivy-module version="2.0">
+ <info organisation="org.acme" module="module2" revision="1.2" />
+</ivy-module>
\ No newline at end of file
diff --git a/test/workspace/repo/org.acme/module2/1.2/module2.jar b/test/workspace/repo/org.acme/module2/1.2/module2.jar
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/workspace/repo/org.acme/module2/1.2/module2.jar