This is an automated email from the ASF dual-hosted git repository. cstamas pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/master by this push: new bf6e6ee00a [MNG-8544] Conflicting extensions went unnoticed (#2063) bf6e6ee00a is described below commit bf6e6ee00a07ad0514b45c79fa76acb17805bf44 Author: Tamas Cservenak <ta...@cservenak.net> AuthorDate: Tue Jan 28 12:54:26 2025 +0100 [MNG-8544] Conflicting extensions went unnoticed (#2063) Now that Maven4 has several extension sources, we need to be able to detect conflicts. For now, we allow only one GA and fail the bootstrapping, if conflict discovered. User should fix the situation, with the help of detailed error message enumerating all the conflicts. --- https://issues.apache.org/jira/browse/MNG-8544 --- .../org/apache/maven/cling/invoker/BaseParser.java | 38 +++++++++++++++++++--- .../maven/cling/invoker/mvn/MavenInvokerTest.java | 33 +++++++++++++++++++ .../cling/invoker/mvn/MavenInvokerTestSupport.java | 2 +- 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java index 27f4add0af..982b1320b7 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java @@ -295,17 +295,45 @@ protected Map<String, String> populateUserProperties(LocalContext context) throw protected List<CoreExtension> readCoreExtensionsDescriptor(LocalContext context) throws ParserException, IOException { - ArrayList<CoreExtension> extensions = new ArrayList<>(); String installationExtensionsFile = context.userProperties.get(Constants.MAVEN_INSTALLATION_EXTENSIONS); - extensions.addAll(readCoreExtensionsDescriptorFromFile( + ArrayList<CoreExtension> installationExtensions = new ArrayList<>(readCoreExtensionsDescriptorFromFile( context.installationDirectory.resolve(installationExtensionsFile))); + String userExtensionsFile = context.userProperties.get(Constants.MAVEN_USER_EXTENSIONS); + ArrayList<CoreExtension> userExtensions = new ArrayList<>( + readCoreExtensionsDescriptorFromFile(context.userHomeDirectory.resolve(userExtensionsFile))); + String projectExtensionsFile = context.userProperties.get(Constants.MAVEN_PROJECT_EXTENSIONS); - extensions.addAll(readCoreExtensionsDescriptorFromFile(context.cwd.resolve(projectExtensionsFile))); + ArrayList<CoreExtension> projectExtensions = + new ArrayList<>(readCoreExtensionsDescriptorFromFile(context.cwd.resolve(projectExtensionsFile))); - String userExtensionsFile = context.userProperties.get(Constants.MAVEN_USER_EXTENSIONS); - extensions.addAll(readCoreExtensionsDescriptorFromFile(context.userHomeDirectory.resolve(userExtensionsFile))); + // merge these 3 but check for GA uniqueness; we don't want to load up same extension w/ different Vs + HashMap<String, String> gas = new HashMap<>(); + ArrayList<String> conflicts = new ArrayList<>(); + + ArrayList<CoreExtension> coreExtensions = + new ArrayList<>(installationExtensions.size() + userExtensions.size() + projectExtensions.size()); + coreExtensions.addAll(mergeExtensions(installationExtensions, installationExtensionsFile, gas, conflicts)); + coreExtensions.addAll(mergeExtensions(userExtensions, userExtensionsFile, gas, conflicts)); + coreExtensions.addAll(mergeExtensions(projectExtensions, projectExtensionsFile, gas, conflicts)); + + if (!conflicts.isEmpty()) { + throw new ParserException("Extension conflicts: " + String.join("; ", conflicts)); + } + return coreExtensions; + } + + private List<CoreExtension> mergeExtensions( + List<CoreExtension> extensions, String extensionsSource, Map<String, String> gas, List<String> conflicts) { + for (CoreExtension extension : extensions) { + String ga = extension.getGroupId() + ":" + extension.getArtifactId(); + if (gas.containsKey(ga)) { + conflicts.add(ga + " from " + extensionsSource + " already specified in " + gas.get(ga)); + } else { + gas.put(ga, extensionsSource); + } + } return extensions; } diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTest.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTest.java index bae4f7015f..21a24bed57 100644 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTest.java +++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTest.java @@ -19,6 +19,7 @@ package org.apache.maven.cling.invoker.mvn; import java.nio.file.FileSystem; +import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; @@ -26,6 +27,7 @@ import com.google.common.jimfs.Jimfs; import org.apache.maven.api.cli.Invoker; import org.apache.maven.api.cli.Parser; +import org.apache.maven.api.cli.ParserException; import org.apache.maven.cling.invoker.ProtoLookup; import org.codehaus.plexus.classworlds.ClassWorld; import org.junit.jupiter.api.Disabled; @@ -34,6 +36,8 @@ import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDir; +import static org.junit.jupiter.api.Assertions.assertThrows; + /** * Local UT. */ @@ -59,6 +63,35 @@ void defaultFs( invoke(cwd, userHome, Arrays.asList("clean", "verify")); } + @Test + void conflictingExtensions( + @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path cwd, + @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path userHome) + throws Exception { + String extensionsXml = + """ + <?xml version="1.0" encoding="UTF-8"?> + <extensions> + <extension> + <groupId>eu.maveniverse.maven.mimir</groupId> + <artifactId>extension3</artifactId> + <version>0.3.4</version> + </extension> + </extensions> + """; + Path dotMvn = cwd.resolve(".mvn"); + Files.createDirectories(dotMvn); + Path projectExtensions = dotMvn.resolve("extensions.xml"); + Files.writeString(projectExtensions, extensionsXml); + + Path userConf = userHome.resolve(".m2"); + Files.createDirectories(userConf); + Path userExtensions = userConf.resolve("extensions.xml"); + Files.writeString(userExtensions, extensionsXml); + + assertThrows(ParserException.class, () -> invoke(cwd, userHome, Arrays.asList("clean", "verify"))); + } + @Disabled("Until we move off fully from File") @Test void jimFs() throws Exception { diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTestSupport.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTestSupport.java index 89b33df73f..15caa92f67 100644 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTestSupport.java +++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTestSupport.java @@ -88,7 +88,7 @@ protected void invoke(Path cwd, Path userHome, Collection<String> goals) throws .resolve("maven.properties")), "${maven.home}/conf/maven.properties must be a file"); - Files.createDirectory(cwd.resolve(".mvn")); + Files.createDirectories(cwd.resolve(".mvn")); Path pom = cwd.resolve("pom.xml").toAbsolutePath(); Files.writeString(pom, POM_STRING); Path appJava = cwd.resolve("src/main/java/org/apache/maven/samples/sample/App.java");