This is an automated email from the ASF dual-hosted git repository.

bodewig pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ant-ivy.git


The following commit(s) were added to refs/heads/master by this push:
     new 5f11f05f IVY-1642 fix POM translation for multiple artifacts in one 
module
5f11f05f is described below

commit 5f11f05fe38b2fc3779d5bec6287505798bc701c
Author: Stefan Bodewig <[email protected]>
AuthorDate: Fri Apr 14 07:05:28 2023 +0200

    IVY-1642 fix POM translation for multiple artifacts in one module
---
 asciidoc/release-notes.adoc                        |  5 +-
 src/java/org/apache/ivy/core/resolve/IvyNode.java  |  6 ++
 .../org/apache/ivy/core/resolve/IvyNodeUsage.java  | 50 ++----------
 .../parser/m2/PomModuleDescriptorBuilder.java      | 24 +++++-
 .../parser/m2/PomModuleDescriptorParserTest.java   | 89 +++++++++++++++++++++
 ...st-dependencies-with-and-without-classifier.pom | 91 ++++++++++++++++++++++
 6 files changed, 220 insertions(+), 45 deletions(-)

diff --git a/asciidoc/release-notes.adoc b/asciidoc/release-notes.adoc
index 84379627..c04489ab 100644
--- a/asciidoc/release-notes.adoc
+++ b/asciidoc/release-notes.adoc
@@ -36,7 +36,8 @@ More information about the project can be found on the 
website link:https://ant.
 
 Key features of this 2.5.2 release are:
 
-- FIX: ivy:retrieve could fail because of a `NullPointerException` 
(jira:IVY-1641[])
+- FIX: reading POMs may loose dependencies when multiple Maven
+  dependencies only differ in `classifier` (jira:IVY-1642[])
 
 == List of Changes in this Release
 
@@ -53,6 +54,8 @@ For details about the following changes, check our JIRA 
install at link:https://
 ////
 
 - FIX: ivy:retrieve could fail because of a `NullPointerException` 
(jira:IVY-1641[])
+- FIX: reading POMs may loose dependencies when multiple Maven
+  dependencies only differ in `classifier` (jira:IVY-1642[])
 
 == Committers and Contributors
 
diff --git a/src/java/org/apache/ivy/core/resolve/IvyNode.java 
b/src/java/org/apache/ivy/core/resolve/IvyNode.java
index 273488ef..c7872c89 100644
--- a/src/java/org/apache/ivy/core/resolve/IvyNode.java
+++ b/src/java/org/apache/ivy/core/resolve/IvyNode.java
@@ -49,6 +49,7 @@ import org.apache.ivy.core.module.descriptor.IncludeRule;
 import org.apache.ivy.core.module.descriptor.MDArtifact;
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.apache.ivy.core.module.id.ArtifactId;
+import org.apache.ivy.core.module.id.ArtifactRevisionId;
 import org.apache.ivy.core.module.id.ModuleId;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.apache.ivy.core.resolve.IvyNodeCallers.Caller;
@@ -873,6 +874,7 @@ public class IvyNode implements Comparable<IvyNode> {
 
         // now exclude artifacts that aren't accepted by any caller
         Iterator<Artifact> iter = artifacts.iterator();
+        Set<ArtifactRevisionId> artifactRevisionsSeen = new 
HashSet<ArtifactRevisionId>();
         while (iter.hasNext()) {
             Artifact artifact = iter.next();
             boolean excluded = callers.doesCallersExclude(rootModuleConf, 
artifact);
@@ -880,6 +882,10 @@ public class IvyNode implements Comparable<IvyNode> {
                 Message.debug(this + " in " + rootModuleConf + ": excluding " 
+ artifact);
                 iter.remove();
             }
+            if (!artifactRevisionsSeen.add(artifact.getId())) {
+                Message.debug(this + " in " + rootModuleConf + ": skipping 
duplicate " + artifact);
+                iter.remove();
+            }
         }
         return artifacts.toArray(new Artifact[artifacts.size()]);
     }
diff --git a/src/java/org/apache/ivy/core/resolve/IvyNodeUsage.java 
b/src/java/org/apache/ivy/core/resolve/IvyNodeUsage.java
index f96beb91..c9676dcc 100644
--- a/src/java/org/apache/ivy/core/resolve/IvyNodeUsage.java
+++ b/src/java/org/apache/ivy/core/resolve/IvyNodeUsage.java
@@ -24,15 +24,10 @@ import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
-import org.apache.ivy.core.module.descriptor.DefaultIncludeRule;
 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;
-import org.apache.ivy.core.module.id.ArtifactId;
-import org.apache.ivy.core.module.id.ModuleId;
-import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
-import org.apache.ivy.plugins.matcher.PatternMatcher;
 
 /**
  * Class collecting usage data for an IvyNode.
@@ -243,35 +238,15 @@ public class IvyNodeUsage {
         if (dependersInConf == null) {
             return null;
         }
-        final Set<IncludeRule> dependencyIncludes = new HashSet<>();
-        // true if the depedency descriptor of any of the depender *doesn't* 
have an explicit
-        // "<artifact>" or an "<include>". false otherwise
-        boolean atLeastOneDependerNeedsAllArtifacts = false;
-        // true if the dependency descriptor of any of the depender either has 
an explicit "<artifact>"
-        // or an "<include>". false otherwise
-        boolean atLeastOneDependerHasSpecificArtifactSelection = false;
-        for (final Depender depender : dependersInConf) {
-            final DependencyArtifactDescriptor dads[] = 
depender.dd.getDependencyArtifacts(depender.dd.getModuleConfigurations());
-            final boolean declaresArtifacts = dads != null && dads.length > 0;
-            final IncludeRule[] rules = 
depender.dd.getIncludeRules(depender.dependerConf);
-            final boolean hasIncludeRule = rules != null && rules.length > 0;
-            if (hasIncludeRule) {
-                dependencyIncludes.addAll(Arrays.asList(rules));
-            }
-            if (declaresArtifacts || hasIncludeRule) {
-                atLeastOneDependerHasSpecificArtifactSelection = true;
-            }
-            if (!hasIncludeRule && !declaresArtifacts) {
-                atLeastOneDependerNeedsAllArtifacts = true;
+        Set<IncludeRule> dependencyIncludes = new HashSet<>();
+        for (Depender depender : dependersInConf) {
+            IncludeRule[] rules = 
depender.dd.getIncludeRules(depender.dependerConf);
+            if (rules == null || rules.length == 0) {
+                // no include rule in at least one depender -> we must include 
everything,
+                // and so return no include rule at all
+                return null;
             }
-        }
-        // so there's at least one depender D1 which has a specific artifact 
dependency and at the
-        // same time there's a depender D2 which doesn't have any explicit 
artifact/includes.
-        // so it is expected that an implicit "include all artifacts" is 
applied so that dependencies
-        // such as D2 get (all) the artifacts that are published by the 
dependency's module
-        if (atLeastOneDependerHasSpecificArtifactSelection && 
atLeastOneDependerNeedsAllArtifacts) {
-            // add a "include all artifacts" rule
-            dependencyIncludes.add(includeAllArtifacts());
+            dependencyIncludes.addAll(Arrays.asList(rules));
         }
         return dependencyIncludes;
     }
@@ -338,13 +313,4 @@ public class IvyNodeUsage {
         return false;
     }
 
-    private static IncludeRule includeAllArtifacts() {
-        final ArtifactId aid = new ArtifactId(
-                new ModuleId(PatternMatcher.ANY_EXPRESSION, 
PatternMatcher.ANY_EXPRESSION),
-                PatternMatcher.ANY_EXPRESSION, PatternMatcher.ANY_EXPRESSION,
-                PatternMatcher.ANY_EXPRESSION);
-        return new DefaultIncludeRule(aid, ExactPatternMatcher.INSTANCE, null);
-    }
-
-
 }
diff --git 
a/src/java/org/apache/ivy/plugins/parser/m2/PomModuleDescriptorBuilder.java 
b/src/java/org/apache/ivy/plugins/parser/m2/PomModuleDescriptorBuilder.java
index 87690f00..3c6eff88 100644
--- a/src/java/org/apache/ivy/plugins/parser/m2/PomModuleDescriptorBuilder.java
+++ b/src/java/org/apache/ivy/plugins/parser/m2/PomModuleDescriptorBuilder.java
@@ -305,6 +305,8 @@ public class PomModuleDescriptorBuilder {
         // the same dependency mrid could appear twice in the module 
descriptor,
         // so we check if we already have created a dependency descriptor for 
the dependency mrid
         final DependencyDescriptor existing = 
this.ivyModuleDescriptor.depDescriptors.get(moduleRevId);
+        final String[] existingConfigurations = existing == null ? new 
String[0]
+            : existing.getModuleConfigurations();
         final DefaultDependencyDescriptor dd = (existing != null && existing 
instanceof DefaultDependencyDescriptor)
                 ? (DefaultDependencyDescriptor) existing
                 : new PomDependencyDescriptor(dep, ivyModuleDescriptor, 
moduleRevId, !excludeAllTransitiveDeps);
@@ -314,7 +316,14 @@ public class PomModuleDescriptorBuilder {
         ConfMapper mapping = MAVEN2_CONF_MAPPING.get(scope);
         mapping.addMappingConfs(dd, dep.isOptional());
         Map<String, String> extraAtt = new HashMap<>();
-        if (dep.getClassifier() != null || dep.getType() != null && 
!"jar".equals(dep.getType())) {
+        final String optionalizedScope = dep.isOptional() ? "optional" : scope;
+        if (isNonDefaultArtifact(dep)) {
+            if (existing != null && 
existing.getAllDependencyArtifacts().length == 0) {
+                String moduleConfiguration = existingConfigurations.length == 1
+                    ? existingConfigurations[0] : optionalizedScope;
+                // previously added dependency has been the "default artifact"
+                dd.addDependencyArtifact(moduleConfiguration, 
createDefaultArtifact(dd));
+            }
             String type = "jar";
             if (dep.getType() != null) {
                 type = dep.getType();
@@ -339,9 +348,11 @@ public class PomModuleDescriptorBuilder {
                     dd, dd.getDependencyId().getName(), type, ext, null, 
extraAtt);
             // here we have to assume a type and ext for the artifact, so this 
is a limitation
             // compared to how m2 behave with classifiers
-            final String optionalizedScope = dep.isOptional() ? "optional" : 
scope;
             depArtifact.addConfiguration(optionalizedScope);
             dd.addDependencyArtifact(optionalizedScope, depArtifact);
+        } else if (existing != null) {
+            // this is the "default" artifact and some non-default artifact 
has already been added
+            dd.addDependencyArtifact(optionalizedScope, 
createDefaultArtifact(dd));
         }
 
         for (ModuleId excludedModule : excluded) {
@@ -362,6 +373,15 @@ public class PomModuleDescriptorBuilder {
         }
     }
 
+    private boolean isNonDefaultArtifact(PomDependencyData dep) {
+        return dep.getClassifier() != null || dep.getType() != null && 
!"jar".equals(dep.getType());
+    }
+
+    private DefaultDependencyArtifactDescriptor 
createDefaultArtifact(DefaultDependencyDescriptor dd) {
+        return new DefaultDependencyArtifactDescriptor(dd, 
dd.getDependencyId().getName(),
+            "jar", "jar", null, null);
+    }
+
     private static boolean shouldExcludeAllTransitiveDeps(final List<ModuleId> 
exclusions) {
         if (exclusions == null || exclusions.isEmpty()) {
             return false;
diff --git 
a/test/java/org/apache/ivy/plugins/parser/m2/PomModuleDescriptorParserTest.java 
b/test/java/org/apache/ivy/plugins/parser/m2/PomModuleDescriptorParserTest.java
index 100fad18..a9673cc0 100644
--- 
a/test/java/org/apache/ivy/plugins/parser/m2/PomModuleDescriptorParserTest.java
+++ 
b/test/java/org/apache/ivy/plugins/parser/m2/PomModuleDescriptorParserTest.java
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.fail;
 
 import java.io.File;
@@ -343,6 +344,94 @@ public class PomModuleDescriptorParserTest extends 
AbstractModuleDescriptorParse
         assertEquals(extraAtt, 
dds[0].getAllDependencyArtifacts()[0].getExtraAttributes());
     }
 
+    /**
+     * @see "https://issues.apache.org/jira/browse/IVY-1642";
+     */
+    @Test
+    public void testDependenciesWithAndWithoutClassifier() throws Exception {
+        ModuleDescriptor md = 
PomModuleDescriptorParser.getInstance().parseDescriptor(settings,
+            
getClass().getResource("test-dependencies-with-and-without-classifier.pom"), 
true);
+        assertNotNull(md);
+
+        assertEquals(ModuleRevisionId.newInstance("org.apache", "test", "1.0"),
+            md.getModuleRevisionId());
+
+        DependencyDescriptor[] dds = md.getDependencies();
+        assertNotNull(dds);
+        assertEquals(5, dds.length);
+        Map<String, String> extraAtt = Collections.singletonMap("classifier", 
"asl");
+        assertEquals(ModuleRevisionId.newInstance("commons-logging", 
"commons-logging", "1.0.4"),
+            dds[0].getDependencyRevisionId());
+        DependencyArtifactDescriptor[] dads = 
dds[0].getAllDependencyArtifacts();
+        assertEquals(2, dads.length);
+        assertEquals(Collections.emptyMap(), dads[0].getExtraAttributes());
+        assertEquals(extraAtt, dads[1].getExtraAttributes());
+
+        assertEquals(ModuleRevisionId.newInstance("commons-logging", 
"commons-logging2", "1.0.4"),
+            dds[1].getDependencyRevisionId());
+        dads = dds[1].getAllDependencyArtifacts();
+        assertEquals(2, dads.length);
+        assertEquals(Collections.emptyMap(), dads[1].getExtraAttributes());
+        assertEquals(extraAtt, dads[0].getExtraAttributes());
+
+        assertEquals(ModuleRevisionId.newInstance("commons-logging", 
"commons-logging3", "1.0.4"),
+            dds[2].getDependencyRevisionId());
+        dads = dds[2].getAllDependencyArtifacts();
+        assertEquals(2, dads.length);
+        assertEquals(extraAtt, dads[0].getExtraAttributes());
+        assertEquals(Collections.singletonMap("classifier", "foo"),
+                     dads[1].getExtraAttributes());
+
+        assertEquals(ModuleRevisionId.newInstance("commons-logging", 
"commons-logging4", "1.0.4"),
+            dds[3].getDependencyRevisionId());
+        dads = dds[3].getAllDependencyArtifacts();
+        assertEquals(2, dads.length);
+        assertEquals(Collections.emptyMap(), dads[0].getExtraAttributes());
+        assertEquals(extraAtt, dads[1].getExtraAttributes());
+        DependencyArtifactDescriptor[] providedDads = 
dds[3].getDependencyArtifacts("provided");
+        assertEquals(1, providedDads.length);
+        assertSame(dads[0], providedDads[0]);
+        DependencyArtifactDescriptor[] compileDads = 
dds[3].getDependencyArtifacts("compile");
+        assertEquals(1, compileDads.length);
+        assertSame(dads[1], compileDads[0]);
+
+        assertEquals(ModuleRevisionId.newInstance("commons-logging", 
"commons-logging5", "1.0.4"),
+            dds[4].getDependencyRevisionId());
+        dads = dds[4].getAllDependencyArtifacts();
+        assertEquals(2, dads.length);
+        assertEquals(extraAtt, dads[0].getExtraAttributes());
+        assertEquals(Collections.emptyMap(), dads[1].getExtraAttributes());
+        providedDads = dds[4].getDependencyArtifacts("provided");
+        assertEquals(1, providedDads.length);
+        assertSame(dads[1], providedDads[0]);
+        compileDads = dds[4].getDependencyArtifacts("compile");
+        assertEquals(1, compileDads.length);
+        assertSame(dads[0], compileDads[0]);
+
+        // now we verify the conversion to an Ivy file
+        PomModuleDescriptorParser.getInstance().toIvyFile(
+            
getClass().getResource("test-dependencies-with-and-without-classifier.pom").openStream(),
+            new 
URLResource(getClass().getResource("test-dependencies-with-and-without-classifier.pom")),
 dest,
+            md);
+
+        assertTrue(dest.exists());
+
+        // the converted Ivy file should be parsable with validate=true
+        ModuleDescriptor md2 = 
XmlModuleDescriptorParser.getInstance().parseDescriptor(
+            new IvySettings(), dest.toURI().toURL(), true);
+
+        // and the parsed module descriptor should be similar to the original
+        assertNotNull(md2);
+        assertEquals(md.getModuleRevisionId(), md2.getModuleRevisionId());
+        dds = md2.getDependencies();
+        assertEquals(5, dds.length);
+        for (int i = 0; i < dds.length; i++) {
+            assertEquals(2, dds[i].getAllDependencyArtifacts().length);
+            int withExt = i == 0 || i == 3 ? 1: 0;
+            assertEquals(extraAtt, 
dds[i].getAllDependencyArtifacts()[withExt].getExtraAttributes());
+        }
+    }
+
     @Test
     public void testDependenciesWithType() throws Exception {
         ModuleDescriptor md = 
PomModuleDescriptorParser.getInstance().parseDescriptor(settings,
diff --git 
a/test/java/org/apache/ivy/plugins/parser/m2/test-dependencies-with-and-without-classifier.pom
 
b/test/java/org/apache/ivy/plugins/parser/m2/test-dependencies-with-and-without-classifier.pom
new file mode 100644
index 00000000..7cf2ff81
--- /dev/null
+++ 
b/test/java/org/apache/ivy/plugins/parser/m2/test-dependencies-with-and-without-classifier.pom
@@ -0,0 +1,91 @@
+<?xml version="1.0"?>
+<!--
+   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
+
+     https://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.
+-->
+<project>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.apache</groupId>
+  <artifactId>test</artifactId>
+  <name>Test Module for Ivy M2 parsing</name>
+  <version>1.0</version>
+  <url>http://ant.apache.org/ivy</url>
+  <organization>
+    <name>Apache</name>
+    <url>http://ant.apache.org/ivy</url>
+  </organization>
+  <dependencies>
+    <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging</artifactId>
+      <version>1.0.4</version>
+    </dependency>
+    <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging</artifactId>
+      <version>1.0.4</version>
+      <classifier>asl</classifier>
+    </dependency>
+    <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging2</artifactId>
+      <version>1.0.4</version>
+      <classifier>asl</classifier>
+    </dependency>
+    <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging2</artifactId>
+      <version>1.0.4</version>
+    </dependency>
+    <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging3</artifactId>
+      <version>1.0.4</version>
+      <classifier>asl</classifier>
+    </dependency>
+    <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging3</artifactId>
+      <version>1.0.4</version>
+      <classifier>foo</classifier>
+    </dependency>
+    <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging4</artifactId>
+      <version>1.0.4</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging4</artifactId>
+      <version>1.0.4</version>
+      <classifier>asl</classifier>
+    </dependency>
+    <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging5</artifactId>
+      <version>1.0.4</version>
+      <classifier>asl</classifier>
+    </dependency>
+    <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging5</artifactId>
+      <version>1.0.4</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+</project>

Reply via email to