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 c93150c61e7f..20247749e243 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 @@ -489,21 +489,21 @@ protected List readCoreExtensionsDescriptor(LocalContext context // project file = context.cwd.resolve(eff.get(Constants.MAVEN_PROJECT_EXTENSIONS)); - loaded = readCoreExtensionsDescriptorFromFile(file); + loaded = readCoreExtensionsDescriptorFromFile(file, false); if (!loaded.isEmpty()) { result.add(new CoreExtensions(file, loaded)); } // user file = context.userHomeDirectory.resolve(eff.get(Constants.MAVEN_USER_EXTENSIONS)); - loaded = readCoreExtensionsDescriptorFromFile(file); + loaded = readCoreExtensionsDescriptorFromFile(file, true); if (!loaded.isEmpty()) { result.add(new CoreExtensions(file, loaded)); } // installation file = context.installationDirectory.resolve(eff.get(Constants.MAVEN_INSTALLATION_EXTENSIONS)); - loaded = readCoreExtensionsDescriptorFromFile(file); + loaded = readCoreExtensionsDescriptorFromFile(file, true); if (!loaded.isEmpty()) { result.add(new CoreExtensions(file, loaded)); } @@ -511,7 +511,7 @@ protected List readCoreExtensionsDescriptor(LocalContext context return result.isEmpty() ? null : List.copyOf(result); } - protected List readCoreExtensionsDescriptorFromFile(Path extensionsFile) { + protected List readCoreExtensionsDescriptorFromFile(Path extensionsFile, boolean allowMetaVersions) { try { if (extensionsFile != null && Files.exists(extensionsFile)) { try (InputStream is = Files.newInputStream(extensionsFile)) { @@ -519,7 +519,8 @@ protected List readCoreExtensionsDescriptorFromFile(Path extensio extensionsFile, List.copyOf(new CoreExtensionsStaxReader() .read(is, true, new InputSource(extensionsFile.toString())) - .getExtensions())); + .getExtensions()), + allowMetaVersions); } } return List.of(); @@ -529,23 +530,37 @@ protected List readCoreExtensionsDescriptorFromFile(Path extensio } protected List validateCoreExtensionsDescriptorFromFile( - Path extensionFile, List coreExtensions) { + Path extensionFile, List coreExtensions, boolean allowMetaVersions) { Map> gasLocations = new HashMap<>(); + Map> metaVersionLocations = new HashMap<>(); for (CoreExtension coreExtension : coreExtensions) { String ga = coreExtension.getGroupId() + ":" + coreExtension.getArtifactId(); InputLocation location = coreExtension.getLocation(""); gasLocations.computeIfAbsent(ga, k -> new ArrayList<>()).add(location); + // TODO: metaversions could be extensible enum with these two values out of the box + if ("LATEST".equals(coreExtension.getVersion()) || "RELEASE".equals(coreExtension.getVersion())) { + metaVersionLocations.computeIfAbsent(ga, k -> new ArrayList<>()).add(location); + } } - if (gasLocations.values().stream().noneMatch(l -> l.size() > 1)) { - return coreExtensions; - } - throw new IllegalStateException("Extension conflicts in file " + extensionFile + ": " - + gasLocations.entrySet().stream() - .map(e -> e.getKey() + " defined on lines " - + e.getValue().stream() - .map(l -> String.valueOf(l.getLineNumber())) - .collect(Collectors.joining(", "))) - .collect(Collectors.joining("; "))); + if (gasLocations.values().stream().anyMatch(l -> l.size() > 1)) { + throw new IllegalStateException("Extension conflicts in file " + extensionFile + ": " + + gasLocations.entrySet().stream() + .map(e -> e.getKey() + " defined on lines " + + e.getValue().stream() + .map(l -> String.valueOf(l.getLineNumber())) + .collect(Collectors.joining(", "))) + .collect(Collectors.joining("; "))); + } + if (!allowMetaVersions && !metaVersionLocations.isEmpty()) { + throw new IllegalStateException("Extension with illegal version in file " + extensionFile + ": " + + metaVersionLocations.entrySet().stream() + .map(e -> e.getKey() + " defined on lines " + + e.getValue().stream() + .map(l -> String.valueOf(l.getLineNumber())) + .collect(Collectors.joining(", "))) + .collect(Collectors.joining("; "))); + } + return coreExtensions; } @Nullable diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/PrecedenceCoreExtensionSelector.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/PrecedenceCoreExtensionSelector.java index 6edfde98eee4..590529d5f193 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/PrecedenceCoreExtensionSelector.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/PrecedenceCoreExtensionSelector.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; +import java.util.Objects; import java.util.Optional; import org.apache.maven.api.cli.CoreExtensions; @@ -60,7 +61,7 @@ protected List selectCoreExtensions(C context, List repositories, RepositorySystemSession session) throws PluginResolutionException { - return resolveInternal(plugin, null /* pluginArtifact */, dependencyFilter, repositories, session); + RequestTrace trace = RequestTrace.newChild(null, plugin); + + Artifact pluginArtifact = toArtifact(plugin, session); + + try { + DefaultRepositorySystemSession pluginSession = new DefaultRepositorySystemSession(session); + pluginSession.setArtifactDescriptorPolicy(new SimpleArtifactDescriptorPolicy(true, false)); + + ArtifactDescriptorRequest request = + new ArtifactDescriptorRequest(pluginArtifact, repositories, REPOSITORY_CONTEXT); + request.setTrace(trace); + ArtifactDescriptorResult result = repoSystem.readArtifactDescriptor(pluginSession, request); + + for (MavenPluginDependenciesValidator dependenciesValidator : dependenciesValidators) { + dependenciesValidator.validate(session, pluginArtifact, result); + } + + pluginArtifact = result.getArtifact(); + + if (logger.isWarnEnabled() && !result.getRelocations().isEmpty()) { + String message = + pluginArtifact instanceof RelocatedArtifact relocated ? ": " + relocated.getMessage() : ""; + logger.warn( + "The extension {} has been relocated to {}{}", + result.getRelocations().get(0), + pluginArtifact, + message); + } + return resolveInternal(plugin, pluginArtifact, dependencyFilter, repositories, session); + } catch (ArtifactDescriptorException e) { + throw new PluginResolutionException(plugin, e.getResult().getExceptions(), e); + } } @Override diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11181CoreExtensionsMetaVersionsTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11181CoreExtensionsMetaVersionsTest.java new file mode 100644 index 000000000000..93c33c9f293d --- /dev/null +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11181CoreExtensionsMetaVersionsTest.java @@ -0,0 +1,116 @@ +/* + * 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.Path; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * This is a test set for GH-11181. + */ +class MavenITgh11181CoreExtensionsMetaVersionsTest extends AbstractMavenIntegrationTestCase { + MavenITgh11181CoreExtensionsMetaVersionsTest() { + super("[4.1.0-SNAPSHOT,)"); + } + + /** + * Project wide extensions: use of meta versions is invalid. + */ + @Test + void pwMetaVersionIsInvalid() throws Exception { + Path testDir = extractResources("/gh-11181-core-extensions-meta-versions") + .toPath() + .toAbsolutePath() + .resolve("pw-metaversion-is-invalid"); + Verifier verifier = newVerifier(testDir.toString()); + verifier.setUserHomeDirectory(testDir.resolve("HOME")); + verifier.setAutoclean(false); + verifier.addCliArgument("validate"); + try { + verifier.execute(); + fail("Expected VerificationException"); + } catch (VerificationException e) { + // there is not even a log; this is very early failure + assertTrue(e.getMessage().contains("Error executing Maven.")); + } + } + + /** + * User wide extensions: use of meta versions is valid. + */ + @Test + void uwMetaVersionIsValid() throws Exception { + Path testDir = extractResources("/gh-11181-core-extensions-meta-versions") + .toPath() + .toAbsolutePath() + .resolve("uw-metaversion-is-valid"); + Verifier verifier = newVerifier(testDir.toString()); + verifier.setUserHomeDirectory(testDir.resolve("HOME")); + verifier.setHandleLocalRepoTail(false); + verifier.setAutoclean(false); + verifier.addCliArgument("validate"); + verifier.execute(); + + verifier.verifyErrorFreeLog(); + } + + /** + * Same GA different V extensions in project-wide and user-wide: warn for conflict. + */ + @Test + void uwPwDifferentVersionIsConflict() throws Exception { + Path testDir = extractResources("/gh-11181-core-extensions-meta-versions") + .toPath() + .toAbsolutePath() + .resolve("uw-pw-different-version-is-conflict"); + Verifier verifier = newVerifier(testDir.toString()); + verifier.setUserHomeDirectory(testDir.resolve("HOME")); + verifier.setHandleLocalRepoTail(false); + verifier.setAutoclean(false); + verifier.addCliArgument("validate"); + verifier.execute(); + + verifier.verifyErrorFreeLog(); + verifier.verifyTextInLog("WARNING"); + verifier.verifyTextInLog("Conflicting extension io.takari.maven:takari-smart-builder"); + } + + /** + * Same GAV extensions in project-wide and user-wide: do not warn for conflict. + */ + @Test + void uwPwSameVersionIsNotConflict() throws Exception { + Path testDir = extractResources("/gh-11181-core-extensions-meta-versions") + .toPath() + .toAbsolutePath() + .resolve("uw-pw-same-version-is-not-conflict"); + Verifier verifier = newVerifier(testDir.toString()); + verifier.setUserHomeDirectory(testDir.resolve("HOME")); + verifier.setHandleLocalRepoTail(false); + verifier.setAutoclean(false); + verifier.addCliArgument("validate"); + verifier.execute(); + + verifier.verifyErrorFreeLog(); + verifier.verifyTextNotInLog("WARNING"); + } +} diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/TestSuiteOrdering.java b/its/core-it-suite/src/test/java/org/apache/maven/it/TestSuiteOrdering.java index 207595094a1b..a0988a2ae1e7 100644 --- a/its/core-it-suite/src/test/java/org/apache/maven/it/TestSuiteOrdering.java +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/TestSuiteOrdering.java @@ -103,6 +103,7 @@ public TestSuiteOrdering() { * the tests are to finishing. Newer tests are also more likely to fail, so this is * a fail fast technique as well. */ + suite.addTestSuite(MavenITgh11181CoreExtensionsMetaVersionsTest.class); suite.addTestSuite(MavenITgh11055DIServiceInjectionTest.class); suite.addTestSuite(MavenITgh11084ReactorReaderPreferConsumerPomTest.class); suite.addTestSuite(MavenITgh10210SettingsXmlDecryptTest.class); diff --git a/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/pw-metaversion-is-invalid/.mvn/extensions.xml b/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/pw-metaversion-is-invalid/.mvn/extensions.xml new file mode 100644 index 000000000000..aeb80a24e7ae --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/pw-metaversion-is-invalid/.mvn/extensions.xml @@ -0,0 +1,8 @@ + + + + io.takari.maven + takari-smart-builder + RELEASE + + \ No newline at end of file diff --git a/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/pw-metaversion-is-invalid/HOME/.placeholder b/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/pw-metaversion-is-invalid/HOME/.placeholder new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/pw-metaversion-is-invalid/pom.xml b/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/pw-metaversion-is-invalid/pom.xml new file mode 100644 index 000000000000..c663f5ff863c --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/pw-metaversion-is-invalid/pom.xml @@ -0,0 +1,19 @@ + + + + 4.0.0 + + org.apache.maven.its.gh11181 + pw-metaversion-is-invalid + 1.0-SNAPSHOT + jar + + + + org.junit.jupiter + junit-jupiter-api + 5.13.4 + test + + + diff --git a/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-metaversion-is-valid/.mvn/.placeholder b/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-metaversion-is-valid/.mvn/.placeholder new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-metaversion-is-valid/HOME/.m2/extensions.xml b/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-metaversion-is-valid/HOME/.m2/extensions.xml new file mode 100644 index 000000000000..aeb80a24e7ae --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-metaversion-is-valid/HOME/.m2/extensions.xml @@ -0,0 +1,8 @@ + + + + io.takari.maven + takari-smart-builder + RELEASE + + \ No newline at end of file diff --git a/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-metaversion-is-valid/pom.xml b/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-metaversion-is-valid/pom.xml new file mode 100644 index 000000000000..f33d5f7ed8a8 --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-metaversion-is-valid/pom.xml @@ -0,0 +1,19 @@ + + + + 4.0.0 + + org.apache.maven.its.gh11181 + uw-metaversion-is-valid + 1.0-SNAPSHOT + jar + + + + org.junit.jupiter + junit-jupiter-api + 5.13.4 + test + + + diff --git a/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-pw-different-version-is-conflict/.mvn/extensions.xml b/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-pw-different-version-is-conflict/.mvn/extensions.xml new file mode 100644 index 000000000000..efadc86545ac --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-pw-different-version-is-conflict/.mvn/extensions.xml @@ -0,0 +1,8 @@ + + + + io.takari.maven + takari-smart-builder + 1.1.0 + + \ No newline at end of file diff --git a/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-pw-different-version-is-conflict/HOME/.m2/extensions.xml b/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-pw-different-version-is-conflict/HOME/.m2/extensions.xml new file mode 100644 index 000000000000..abe8e2d3ae8f --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-pw-different-version-is-conflict/HOME/.m2/extensions.xml @@ -0,0 +1,8 @@ + + + + io.takari.maven + takari-smart-builder + 1.0.2 + + \ No newline at end of file diff --git a/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-pw-different-version-is-conflict/pom.xml b/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-pw-different-version-is-conflict/pom.xml new file mode 100644 index 000000000000..f33d5f7ed8a8 --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-pw-different-version-is-conflict/pom.xml @@ -0,0 +1,19 @@ + + + + 4.0.0 + + org.apache.maven.its.gh11181 + uw-metaversion-is-valid + 1.0-SNAPSHOT + jar + + + + org.junit.jupiter + junit-jupiter-api + 5.13.4 + test + + + diff --git a/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-pw-same-version-is-not-conflict/.mvn/extensions.xml b/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-pw-same-version-is-not-conflict/.mvn/extensions.xml new file mode 100644 index 000000000000..efadc86545ac --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-pw-same-version-is-not-conflict/.mvn/extensions.xml @@ -0,0 +1,8 @@ + + + + io.takari.maven + takari-smart-builder + 1.1.0 + + \ No newline at end of file diff --git a/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-pw-same-version-is-not-conflict/HOME/.m2/extensions.xml b/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-pw-same-version-is-not-conflict/HOME/.m2/extensions.xml new file mode 100644 index 000000000000..efadc86545ac --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-pw-same-version-is-not-conflict/HOME/.m2/extensions.xml @@ -0,0 +1,8 @@ + + + + io.takari.maven + takari-smart-builder + 1.1.0 + + \ No newline at end of file diff --git a/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-pw-same-version-is-not-conflict/pom.xml b/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-pw-same-version-is-not-conflict/pom.xml new file mode 100644 index 000000000000..f33d5f7ed8a8 --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11181-core-extensions-meta-versions/uw-pw-same-version-is-not-conflict/pom.xml @@ -0,0 +1,19 @@ + + + + 4.0.0 + + org.apache.maven.its.gh11181 + uw-metaversion-is-valid + 1.0-SNAPSHOT + jar + + + + org.junit.jupiter + junit-jupiter-api + 5.13.4 + test + + +