diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomArtifactTransformer.java b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomArtifactTransformer.java index 60ab919f5a78..7a1d32e07710 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomArtifactTransformer.java +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomArtifactTransformer.java @@ -36,7 +36,6 @@ import org.apache.maven.api.feature.Features; import org.apache.maven.api.model.Model; -import org.apache.maven.api.services.ModelBuilder; import org.apache.maven.api.services.ModelBuilderException; import org.apache.maven.internal.transformation.ConsumerPomArtifactTransformer; import org.apache.maven.model.v4.MavenStaxWriter; @@ -77,8 +76,7 @@ public void injectTransformedArtifacts(RepositorySystemSession session, MavenPro // If there is no build POM there is no reason to inject artifacts for the consumer POM. return; } - boolean isModel40 = ModelBuilder.MODEL_VERSION_4_0_0.equals(project.getModelVersion()); - if (Features.consumerPom(session.getUserProperties(), !isModel40)) { + if (Features.consumerPom(session.getUserProperties(), true)) { Path buildDir = project.getBuild() != null ? Paths.get(project.getBuild().getDirectory()) : null; if (buildDir != null) { diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java index ceec7f761b9e..ef3689cc6907 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java @@ -23,9 +23,15 @@ import java.nio.file.Path; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.function.Function; import java.util.stream.Collectors; +import org.apache.maven.api.ArtifactCoordinates; +import org.apache.maven.api.Node; +import org.apache.maven.api.PathScope; import org.apache.maven.api.SessionData; import org.apache.maven.api.model.Dependency; import org.apache.maven.api.model.DependencyManagement; @@ -77,20 +83,100 @@ public Model build(RepositorySystemSession session, MavenProject project, Path s protected Model buildPom(RepositorySystemSession session, MavenProject project, Path src) throws ModelBuilderException { - ModelBuilderResult result = buildModel(session, project, src); + ModelBuilderResult result = buildModel(session, src); Model model = result.getRawModel(); return transform(model, project); } protected Model buildNonPom(RepositorySystemSession session, MavenProject project, Path src) throws ModelBuilderException { - ModelBuilderResult result = buildModel(session, project, src); - Model model = result.getEffectiveModel(); + Model model = buildEffectiveModel(session, src); return transform(model, project); } - private ModelBuilderResult buildModel(RepositorySystemSession session, MavenProject project, Path src) - throws ModelBuilderException { + private Model buildEffectiveModel(RepositorySystemSession session, Path src) throws ModelBuilderException { + InternalSession iSession = InternalSession.from(session); + ModelBuilderResult result = buildModel(session, src); + Model model = result.getEffectiveModel(); + + if (model.getDependencyManagement() != null + && !model.getDependencyManagement().getDependencies().isEmpty()) { + ArtifactCoordinates artifact = iSession.createArtifactCoordinates( + model.getGroupId(), model.getArtifactId(), model.getVersion(), null); + Node node = iSession.collectDependencies( + iSession.createDependencyCoordinates(artifact), PathScope.TEST_RUNTIME); + + Map nodes = node.stream() + .collect(Collectors.toMap(n -> getDependencyKey(n.getDependency()), Function.identity())); + Map directDependencies = model.getDependencies().stream() + .filter(dependency -> !"import".equals(dependency.getScope())) + .collect(Collectors.toMap( + DefaultConsumerPomBuilder::getDependencyKey, + Function.identity(), + this::merge, + LinkedHashMap::new)); + Map managedDependencies = model.getDependencyManagement().getDependencies().stream() + .filter(dependency -> + nodes.containsKey(getDependencyKey(dependency)) && !"import".equals(dependency.getScope())) + .collect(Collectors.toMap( + DefaultConsumerPomBuilder::getDependencyKey, + Function.identity(), + this::merge, + LinkedHashMap::new)); + + // for each managed dep in the model: + // * if there is no corresponding node in the tree, discard the managed dep + // * if there's a direct dependency, apply the managed dependency to it and discard the managed dep + // * else keep the managed dep + managedDependencies.keySet().retainAll(nodes.keySet()); + + directDependencies.replaceAll((key, dependency) -> { + var managedDependency = managedDependencies.get(key); + if (managedDependency != null) { + if (dependency.getVersion() == null && managedDependency.getVersion() != null) { + dependency = dependency.withVersion(managedDependency.getVersion()); + } + if (dependency.getScope() == null && managedDependency.getScope() != null) { + dependency = dependency.withScope(managedDependency.getScope()); + } + if (dependency.getOptional() == null && managedDependency.getOptional() != null) { + dependency = dependency.withOptional(managedDependency.getOptional()); + } + if (dependency.getExclusions().isEmpty() + && !managedDependency.getExclusions().isEmpty()) { + dependency = dependency.withExclusions(managedDependency.getExclusions()); + } + } + return dependency; + }); + managedDependencies.keySet().removeAll(directDependencies.keySet()); + + model = model.withDependencyManagement( + managedDependencies.isEmpty() + ? null + : model.getDependencyManagement().withDependencies(managedDependencies.values())) + .withDependencies(directDependencies.isEmpty() ? null : directDependencies.values()); + } + + return model; + } + + private Dependency merge(Dependency dep1, Dependency dep2) { + throw new IllegalArgumentException("Duplicate dependency: " + dep1); + } + + private static String getDependencyKey(org.apache.maven.api.Dependency dependency) { + return dependency.getGroupId() + ":" + dependency.getArtifactId() + ":" + dependency.getType() + ":" + + dependency.getClassifier(); + } + + private static String getDependencyKey(Dependency dependency) { + return dependency.getGroupId() + ":" + dependency.getArtifactId() + ":" + + (dependency.getType() != null ? dependency.getType() : "") + ":" + + (dependency.getClassifier() != null ? dependency.getClassifier() : ""); + } + + private ModelBuilderResult buildModel(RepositorySystemSession session, Path src) throws ModelBuilderException { InternalSession iSession = InternalSession.from(session); ModelBuilderRequest.ModelBuilderRequestBuilder request = ModelBuilderRequest.builder(); request.requestType(ModelBuilderRequest.RequestType.BUILD_CONSUMER); @@ -102,7 +188,8 @@ private ModelBuilderResult buildModel(RepositorySystemSession session, MavenProj request.lifecycleBindingsInjector(lifecycleBindingsInjector::injectLifecycleBindings); ModelBuilder.ModelBuilderSession mbSession = iSession.getData().get(SessionData.key(ModelBuilder.ModelBuilderSession.class)); - return mbSession.build(request.build()); + ModelBuilderResult result = mbSession.build(request.build()); + return result; } static Model transform(Model model, MavenProject project) { @@ -148,12 +235,16 @@ static Model transform(Model model, MavenProject project) { model = model.withModelVersion(modelVersion); } else { Model.Builder builder = prune( - Model.newBuilder(model, true) - .preserveModelVersion(false) - .root(false) - .parent(null) - .build(null), - model); + Model.newBuilder(model, true) + .preserveModelVersion(false) + .root(false) + .parent(null) + .build(null), + model) + .developers(null) + .contributors(null) + .mailingLists(null) + .issueManagement(null); builder.profiles(prune(model.getProfiles())); model = builder.build(); @@ -210,8 +301,8 @@ private static T prune(T builder, ModelBase model) .build()); } // only keep repositories other than 'central' - builder.pluginRepositories(pruneRepositories(model.getPluginRepositories())); builder.repositories(pruneRepositories(model.getRepositories())); + builder.pluginRepositories(null); return builder; } diff --git a/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/AbstractRepositoryTestCase.java b/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/AbstractRepositoryTestCase.java index ad4aec5741d8..d20e157c258b 100644 --- a/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/AbstractRepositoryTestCase.java +++ b/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/AbstractRepositoryTestCase.java @@ -30,6 +30,7 @@ import org.apache.maven.impl.DefaultRepositoryFactory; import org.apache.maven.impl.InternalSession; import org.apache.maven.impl.model.DefaultInterpolator; +import org.apache.maven.impl.resolver.scopes.Maven4ScopeManagerConfiguration; import org.apache.maven.internal.impl.DefaultSession; import org.codehaus.plexus.PlexusContainer; import org.codehaus.plexus.testing.PlexusTest; @@ -40,6 +41,7 @@ import org.eclipse.aether.internal.impl.DefaultChecksumPolicyProvider; import org.eclipse.aether.internal.impl.DefaultRemoteRepositoryManager; import org.eclipse.aether.internal.impl.DefaultUpdatePolicyAnalyzer; +import org.eclipse.aether.internal.impl.scope.ScopeManagerImpl; import org.eclipse.aether.repository.LocalRepository; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.transfer.TransferListener; @@ -67,8 +69,9 @@ protected PlexusContainer getContainer() { return container; } - public static RepositorySystemSession newMavenRepositorySystemSession(RepositorySystem system) { + public RepositorySystemSession newMavenRepositorySystemSession(RepositorySystem system) { DefaultRepositorySystemSession rsession = new DefaultRepositorySystemSession(h -> false); + rsession.setScopeManager(new ScopeManagerImpl(Maven4ScopeManagerConfiguration.INSTANCE)); LocalRepository localRepo = new LocalRepository("target/local-repo"); rsession.setLocalRepositoryManager(system.newLocalRepositoryManager(rsession, localRepo)); @@ -78,21 +81,20 @@ public static RepositorySystemSession newMavenRepositorySystemSession(Repository DefaultMavenExecutionRequest request = new DefaultMavenExecutionRequest(); MavenSession mavenSession = new MavenSession(rsession, request, new DefaultMavenExecutionResult()); - DefaultSession session = new DefaultSession( - mavenSession, - null, - null, - null, - new SimpleLookup(List.of( - new DefaultRepositoryFactory(new DefaultRemoteRepositoryManager( - new DefaultUpdatePolicyAnalyzer(), new DefaultChecksumPolicyProvider())), - new DefaultInterpolator())), - null); + DefaultSession session = + new DefaultSession(mavenSession, null, null, null, new SimpleLookup(getSessionServices()), null); InternalSession.associate(rsession, session); return rsession; } + protected List getSessionServices() { + return List.of( + new DefaultRepositoryFactory(new DefaultRemoteRepositoryManager( + new DefaultUpdatePolicyAnalyzer(), new DefaultChecksumPolicyProvider())), + new DefaultInterpolator()); + } + public static RemoteRepository newTestRepository() throws MalformedURLException { return new RemoteRepository.Builder( "repo", diff --git a/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java b/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java index ef8ee384ee8e..890aef4cc0b7 100644 --- a/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java +++ b/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java @@ -22,13 +22,19 @@ import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; +import org.apache.maven.api.DependencyCoordinates; +import org.apache.maven.api.Node; +import org.apache.maven.api.PathScope; import org.apache.maven.api.RemoteRepository; import org.apache.maven.api.Session; import org.apache.maven.api.SessionData; import org.apache.maven.api.model.Model; +import org.apache.maven.api.services.DependencyResolver; +import org.apache.maven.api.services.DependencyResolverResult; import org.apache.maven.api.services.ModelBuilder; import org.apache.maven.api.services.ModelBuilderRequest; import org.apache.maven.api.services.ModelSource; @@ -36,12 +42,18 @@ import org.apache.maven.api.services.model.ModelResolverException; import org.apache.maven.di.Injector; import org.apache.maven.execution.MavenExecutionRequest; +import org.apache.maven.impl.DefaultArtifactCoordinatesFactory; +import org.apache.maven.impl.DefaultDependencyCoordinatesFactory; +import org.apache.maven.impl.DefaultModelVersionParser; +import org.apache.maven.impl.DefaultVersionParser; import org.apache.maven.impl.InternalSession; +import org.apache.maven.impl.resolver.MavenVersionScheme; import org.apache.maven.internal.impl.InternalMavenSession; import org.apache.maven.internal.transformation.AbstractRepositoryTestCase; import org.apache.maven.project.MavenProject; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -63,6 +75,30 @@ void setupTransformerContext() throws Exception { iSession.getData().set(SessionData.key(ModelResolver.class), new MyModelResolver()); } + @Override + protected List getSessionServices() { + List services = new ArrayList<>(super.getSessionServices()); + + DependencyResolver dependencyResolver = Mockito.mock(DependencyResolver.class); + DependencyResolverResult resolverResult = Mockito.mock(DependencyResolverResult.class); + Mockito.when(dependencyResolver.collect( + Mockito.any(Session.class), + Mockito.any(DependencyCoordinates.class), + Mockito.any(PathScope.class))) + .thenReturn(resolverResult); + Node node = Mockito.mock(Node.class); + Mockito.when(resolverResult.getRoot()).thenReturn(node); + Node child = Mockito.mock(Node.class); + Mockito.when(node.getChildren()).thenReturn(List.of(child)); + + services.addAll(List.of( + new DefaultArtifactCoordinatesFactory(), + new DefaultDependencyCoordinatesFactory(), + new DefaultVersionParser(new DefaultModelVersionParser(new MavenVersionScheme())), + dependencyResolver)); + return services; + } + @Test void testTrivialConsumer() throws Exception { InternalMavenSession.from(InternalSession.from(session)) diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8527ConsumerPomTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8527ConsumerPomTest.java new file mode 100644 index 000000000000..f5c87152e94c --- /dev/null +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8527ConsumerPomTest.java @@ -0,0 +1,94 @@ +/* + * 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.maven.it; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * This is a test set for MNG-8527. + */ +class MavenITmng8527ConsumerPomTest extends AbstractMavenIntegrationTestCase { + + MavenITmng8527ConsumerPomTest() { + super("[4.0.0-rc-3-SNAPSHOT,)"); + } + + /** + * Verify project is buildable. + */ + @Test + void testIt() throws Exception { + Path basedir = + extractResources("/mng-8527-consumer-pom").getAbsoluteFile().toPath(); + + Verifier verifier = newVerifier(basedir.toString()); + verifier.addCliArgument("install"); + verifier.execute(); + verifier.verifyErrorFreeLog(); + + Path consumerPomPath = + Paths.get(verifier.getArtifactPath("org.apache.maven.its.mng-8527", "child", "1.0.0-SNAPSHOT", "pom")); + Path buildPomPath = Paths.get( + verifier.getArtifactPath("org.apache.maven.its.mng-8527", "child", "1.0.0-SNAPSHOT", "pom", "build")); + + assertTrue(Files.exists(consumerPomPath), "consumer pom not found at " + consumerPomPath); + assertTrue(Files.exists(buildPomPath), "consumer pom not found at " + consumerPomPath); + + List consumerPomLines; + try (Stream lines = Files.lines(consumerPomPath)) { + consumerPomLines = lines.toList(); + } + assertTrue( + consumerPomLines.stream().noneMatch(s -> s.contains("")), + "Consumer pom should not have any element"); + assertTrue( + consumerPomLines.stream().anyMatch(s -> s.contains("")), + "Consumer pom should have an element"); + assertEquals( + 2, + consumerPomLines.stream() + .filter(s -> s.contains("")) + .count(), + "Consumer pom should have two dependencies"); + + List buildPomLines; + try (Stream lines = Files.lines(buildPomPath)) { + buildPomLines = lines.toList(); + } + assertTrue( + buildPomLines.stream().anyMatch(s -> s.contains("")), + "Build pom should have a element"); + assertTrue( + buildPomLines.stream().noneMatch(s -> s.contains("")), + "Build pom should not have an element"); + assertEquals( + 2, + buildPomLines.stream().filter(s -> s.contains("")).count(), + "Build pom should have two dependencies"); + } +} diff --git a/its/core-it-suite/src/test/resources/mng-8527-consumer-pom/.mvn/.gitkeep b/its/core-it-suite/src/test/resources/mng-8527-consumer-pom/.mvn/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/its/core-it-suite/src/test/resources/mng-8527-consumer-pom/child/pom.xml b/its/core-it-suite/src/test/resources/mng-8527-consumer-pom/child/pom.xml new file mode 100644 index 000000000000..517ef640c4a1 --- /dev/null +++ b/its/core-it-suite/src/test/resources/mng-8527-consumer-pom/child/pom.xml @@ -0,0 +1,44 @@ + + + + + 4.0.0 + + org.apache.maven.its.mng-8527 + parent + 1.0.0-SNAPSHOT + + + child + + + + org.apache.maven + maven-api-core + ${mavenVersion} + + + org.junit.jupiter + junit-jupiter-api + test + + + + diff --git a/its/core-it-suite/src/test/resources/mng-8527-consumer-pom/child/src/main/java/Test.java b/its/core-it-suite/src/test/resources/mng-8527-consumer-pom/child/src/main/java/Test.java new file mode 100644 index 000000000000..2fc2e941a20f --- /dev/null +++ b/its/core-it-suite/src/test/resources/mng-8527-consumer-pom/child/src/main/java/Test.java @@ -0,0 +1,19 @@ +/* + * 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. + */ +public class Test {} diff --git a/its/core-it-suite/src/test/resources/mng-8527-consumer-pom/pom.xml b/its/core-it-suite/src/test/resources/mng-8527-consumer-pom/pom.xml new file mode 100644 index 000000000000..f1c79f062d41 --- /dev/null +++ b/its/core-it-suite/src/test/resources/mng-8527-consumer-pom/pom.xml @@ -0,0 +1,72 @@ + + + + + 4.0.0 + + org.apache.maven + maven-parent + 43 + + + org.apache.maven.its.mng-8527 + parent + 1.0.0-SNAPSHOT + pom + + + child + + + + 4.0.0-rc-2 + 17 + + + + + org.junit + junit-bom + 5.11.4 + pom + import + + + + + + + + org.apache.rat + apache-rat-plugin + + + rat-check + + check + + none + + + + + + +