Specifies a comma-separated list of GAV patterns to consider
+ * when looking for updates. If the trailing parts of the GAV are omitted, then can assume any value.
+ *
+ * The wildcard "*" can be used as the only, first, last or both characters in each token.
+ * The version token does support version ranges.
+ *
+ * Examples: {@code "mygroup:artifact:*"}, {@code "mygroup:artifact"}, {@code "mygroup"}
+ *
+ * @since 2.15.0
+ */
+ @Parameter(property = "extensionIncludes", defaultValue = WildcardMatcher.WILDCARD)
+ private List extensionIncludes;
+
+ /**
+ * Specifies a comma-separated list of GAV patterns to NOT consider
+ * when looking for updates. If the trailing parts of the GAV are omitted, then can assume any value.
+ *
+ * This list is taken into account after {@link #extensionIncludes}
.
+ *
+ * The wildcard "*" can be used as the only, first, last or both characters in each token.
+ * The version token does support version ranges.
+ *
+ * Examples: {@code "mygroup:artifact:*"}, {@code "mygroup:artifact"}, {@code "mygroup"}
+ *
+ * @since 2.15.0
+ */
+ @Parameter(property = "extensionExcludes")
+ private List extensionExcludes;
+
+ /**
+ * Whether to allow the major version number to be changed.
+ *
+ * @since 2.15.0
+ */
+ @Parameter(property = "allowMajorUpdates", defaultValue = "true")
+ private boolean allowMajorUpdates = true;
+
+ /**
+ * Whether to allow the minor version number to be changed.
+ *
+ * Note: {@code false} also implies {@linkplain #allowMajorUpdates}
+ * to be {@code false}
+ *
+ * @since 2.15.0
+ */
+ @Parameter(property = "allowMinorUpdates", defaultValue = "true")
+ private boolean allowMinorUpdates = true;
+
+ /**
+ * Whether to allow the incremental version number to be changed.
+ *
+ * Note: {@code false} also implies {@linkplain #allowMajorUpdates}
+ * and {@linkplain #allowMinorUpdates} to be {@code false}
+ *
+ * @since 2.15.0
+ */
+ @Parameter(property = "allowIncrementalUpdates", defaultValue = "true")
+ private boolean allowIncrementalUpdates = true;
+
+ /**
+ * Whether to process core extensions. Default is {@code true}.
+ * @since 2.15.0
+ */
+ @Parameter(property = "processCoreExtensions", defaultValue = "true")
+ private boolean processCoreExtensions = true;
+
+ /**
+ * Whether to process build extensions. Default is {@code true}.
+ * @since 2.15.0
+ */
+ @Parameter(property = "processBuildExtensions", defaultValue = "true")
+ private boolean processBuildExtensions = true;
+
+ /**
+ * Whether to show additional information such as extensions that do not need updating. Defaults to false.
+ *
+ * @since 2.15.0
+ */
+ @Parameter(property = "verbose", defaultValue = "false")
+ private boolean verbose;
+
+ @Inject
+ public DisplayExtensionUpdatesMojo(
+ RepositorySystem repositorySystem,
+ org.eclipse.aether.RepositorySystem aetherRepositorySystem,
+ Map wagonMap,
+ Map changeRecorders) {
+ super(repositorySystem, aetherRepositorySystem, wagonMap, changeRecorders);
+ }
+
+ @Override
+ public void execute() throws MojoExecutionException, MojoFailureException {
+ logInit();
+ validateInput();
+
+ if (!processCoreExtensions && !processBuildExtensions) {
+ getLog().info("Neither core nor build extensions are to be processed. Nothing to do.");
+ return;
+ }
+
+ DependencyFilter includeFilter = DependencyFilter.parseFrom(extensionIncludes);
+ DependencyFilter excludeFilter = DependencyFilter.parseFrom(extensionExcludes);
+
+ Set dependencies;
+ try {
+ Stream extensions;
+ if (processCoreExtensions) {
+ extensions = CoreExtensionUtils.getCoreExtensions(session);
+ } else {
+ extensions = Stream.empty();
+ }
+ if (processBuildExtensions) {
+ extensions = Stream.concat(extensions, session.getCurrentProject().getBuildExtensions().stream());
+ }
+
+ dependencies = extensions
+ .map(e -> DependencyBuilder.newBuilder()
+ .withGroupId(e.getGroupId())
+ .withArtifactId(e.getArtifactId())
+ .withVersion(e.getVersion())
+ .build())
+ .filter(includeFilter::matchersMatch)
+ .filter(excludeFilter::matchersDontMatch)
+ .collect(Collectors.toSet());
+ } catch (IOException | XmlPullParserException e) {
+ throw new MojoExecutionException(e.getMessage());
+ }
+ if (dependencies.isEmpty()) {
+ getLog().info("Extensions set filtered by include- and extensions-filters is empty. Nothing to do.");
+ return;
+ }
+
+ try {
+ logUpdates(getHelper().lookupDependenciesUpdates(dependencies, true, true, allowSnapshots));
+ } catch (VersionRetrievalException e) {
+ throw new MojoExecutionException(e.getMessage(), e);
+ }
+ }
+
+ private Optional calculateUpdateScope() {
+ return of(SegmentUtils.determineUnchangedSegment(
+ allowMajorUpdates, allowMinorUpdates, allowIncrementalUpdates, getLog())
+ .map(s -> Segment.of(s.value() + 1))
+ .orElse(MAJOR));
+ }
+
+ private void logUpdates(Map updates) {
+ List withUpdates = new ArrayList<>();
+ List usingCurrent = new ArrayList<>();
+ for (ArtifactVersions versions : updates.values()) {
+ String left = " " + ArtifactUtils.versionlessKey(versions.getArtifact()) + " ";
+ final String current;
+ ArtifactVersion latest;
+ if (versions.isCurrentVersionDefined()) {
+ current = versions.getCurrentVersion().toString();
+ latest = versions.getNewestUpdate(calculateUpdateScope(), allowSnapshots);
+ } else {
+ ArtifactVersion newestVersion =
+ versions.getNewestVersion(versions.getArtifact().getVersionRange(), allowSnapshots);
+ current = versions.getArtifact().getVersionRange().toString();
+ latest = newestVersion == null
+ ? null
+ : versions.getNewestUpdate(newestVersion, calculateUpdateScope(), allowSnapshots);
+ if (latest != null
+ && ArtifactVersions.isVersionInRange(
+ latest, versions.getArtifact().getVersionRange())) {
+ latest = null;
+ }
+ }
+ String right = " " + (latest == null ? current : current + " -> " + latest);
+ List t = latest == null ? usingCurrent : withUpdates;
+ if (right.length() + left.length() + 3 > INFO_PAD_SIZE + getOutputLineWidthOffset()) {
+ t.add(left + "...");
+ t.add(StringUtils.leftPad(right, INFO_PAD_SIZE + getOutputLineWidthOffset()));
+
+ } else {
+ t.add(StringUtils.rightPad(left, INFO_PAD_SIZE + getOutputLineWidthOffset() - right.length(), ".")
+ + right);
+ }
+ }
+
+ if (verbose) {
+ if (usingCurrent.isEmpty()) {
+ if (!withUpdates.isEmpty()) {
+ logLine(false, "No extensions are using the newest version.");
+ logLine(false, "");
+ }
+ } else {
+ logLine(false, "The following extensions are using the newest version:");
+ for (String s : usingCurrent) {
+ logLine(false, s);
+ }
+ logLine(false, "");
+ }
+ }
+
+ if (withUpdates.isEmpty()) {
+ if (!usingCurrent.isEmpty()) {
+ logLine(false, "No extensions have newer versions.");
+ logLine(false, "");
+ }
+ } else {
+ logLine(false, "The following extensions have newer versions:");
+ for (String withUpdate : withUpdates) {
+ logLine(false, withUpdate);
+ }
+ logLine(false, "");
+ }
+ }
+
+ /**
+ * @param pom the pom to update.
+ * @see AbstractVersionsUpdaterMojo#update(ModifiedPomXMLEventReader)
+ * @since 1.0-alpha-1
+ */
+ @Override
+ protected void update(ModifiedPomXMLEventReader pom) {
+ // do nothing
+ }
+}
diff --git a/versions-maven-plugin/src/test/java/org/codehaus/mojo/versions/DisplayExtensionUpdatesMojoTest.java b/versions-maven-plugin/src/test/java/org/codehaus/mojo/versions/DisplayExtensionUpdatesMojoTest.java
new file mode 100644
index 0000000000..75ac42bd23
--- /dev/null
+++ b/versions-maven-plugin/src/test/java/org/codehaus/mojo/versions/DisplayExtensionUpdatesMojoTest.java
@@ -0,0 +1,156 @@
+package org.codehaus.mojo.versions;
+
+/*
+ * Copyright MojoHaus and Contributors
+ *
+ * Licensed 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.
+ */
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.HashMap;
+
+import org.apache.maven.model.Build;
+import org.apache.maven.model.Model;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.mojo.versions.utils.ExtensionBuilder;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singleton;
+import static java.util.Collections.singletonList;
+import static org.apache.maven.plugin.testing.ArtifactStubFactory.setVariableValueToObject;
+import static org.codehaus.mojo.versions.utils.MockUtils.mockAetherRepositorySystem;
+import static org.codehaus.mojo.versions.utils.MockUtils.mockMavenSession;
+import static org.codehaus.mojo.versions.utils.MockUtils.mockRepositorySystem;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.empty;
+
+/**
+ * Basic tests for {@linkplain DisplayExtensionUpdatesMojo}.
+ *
+ * @author Andrzej Jarmoniuk
+ */
+public class DisplayExtensionUpdatesMojoTest {
+ private DisplayExtensionUpdatesMojo mojo;
+ private Path tempPath;
+
+ @Before
+ public void setUp() throws IllegalAccessException, IOException {
+ mojo = new DisplayExtensionUpdatesMojo(mockRepositorySystem(), mockAetherRepositorySystem(), null, null);
+ mojo.project = new MavenProject() {
+ {
+ setModel(new Model() {
+ {
+ setGroupId("default-group");
+ setArtifactId("default-artifact");
+ setVersion("1.0.0");
+ }
+ });
+ }
+ };
+ mojo.project.setRemoteArtifactRepositories(emptyList());
+ mojo.project.setPluginArtifactRepositories(emptyList());
+ mojo.session = mockMavenSession(mojo.project);
+ tempPath = Files.createTempFile("display-extension-updates-", ".log");
+ mojo.outputFile = tempPath.toFile();
+ mojo.outputEncoding = "UTF-8";
+
+ mojo.setPluginContext(new HashMap() {
+ {
+ put(
+ "org.codehaus.mojo.versions.AbstractVersionsDisplayMojo.outputFile",
+ singleton(tempPath.toFile().getCanonicalPath()));
+ }
+ });
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ Files.deleteIfExists(tempPath);
+ }
+
+ @Test
+ public void testNoBuildExists()
+ throws MojoExecutionException, MojoFailureException, IllegalAccessException, IOException {
+ setVariableValueToObject(mojo, "extensionIncludes", singletonList("*"));
+ setVariableValueToObject(mojo, "extensionExcludes", emptyList());
+ mojo.execute();
+ }
+
+ @Test
+ public void testIncludesMakesSetEmpty()
+ throws MojoExecutionException, MojoFailureException, IllegalAccessException, IOException {
+ setVariableValueToObject(mojo, "extensionIncludes", singletonList("other-group"));
+ setVariableValueToObject(mojo, "extensionExcludes", emptyList());
+
+ mojo.getProject().setBuild(new Build());
+ mojo.getProject()
+ .getBuild()
+ .setExtensions(Collections.singletonList(ExtensionBuilder.newBuilder()
+ .withGroupId("default-group")
+ .withArtifactId("artifactA")
+ .withVersion("1.0.0")
+ .build()));
+ mojo.execute();
+
+ assertThat(Files.readAllLines(tempPath), empty());
+ }
+
+ @Test
+ public void testIncludesMakesSetNonEmpty()
+ throws MojoExecutionException, MojoFailureException, IllegalAccessException, IOException {
+ setVariableValueToObject(mojo, "extensionIncludes", singletonList("default-group"));
+ setVariableValueToObject(mojo, "extensionExcludes", emptyList());
+
+ mojo.getProject().setBuild(new Build());
+ mojo.getProject()
+ .getBuild()
+ .setExtensions(Collections.singletonList(ExtensionBuilder.newBuilder()
+ .withGroupId("default-group")
+ .withArtifactId("artifactA")
+ .withVersion("1.0.0")
+ .build()));
+ mojo.execute();
+
+ assertThat(
+ String.join("", Files.readAllLines(tempPath)),
+ containsString("default-group:artifactA ... 1.0.0 -> 2.0.0"));
+ }
+
+ @Test
+ public void testIncludesExcludesMakesSetEmpty()
+ throws MojoExecutionException, MojoFailureException, IllegalAccessException, IOException {
+ setVariableValueToObject(mojo, "extensionIncludes", singletonList("default-group"));
+ setVariableValueToObject(mojo, "extensionExcludes", singletonList("default-group:artifactA"));
+
+ mojo.getProject().setBuild(new Build());
+ mojo.getProject()
+ .getBuild()
+ .setExtensions(Collections.singletonList(ExtensionBuilder.newBuilder()
+ .withGroupId("default-group")
+ .withArtifactId("artifactA")
+ .withVersion("1.0.0")
+ .build()));
+ mojo.execute();
+
+ assertThat(Files.readAllLines(tempPath), empty());
+ }
+}
diff --git a/versions-test/src/main/java/org/codehaus/mojo/versions/utils/MockUtils.java b/versions-test/src/main/java/org/codehaus/mojo/versions/utils/MockUtils.java
index 92a1d0cab7..f604c6f903 100644
--- a/versions-test/src/main/java/org/codehaus/mojo/versions/utils/MockUtils.java
+++ b/versions-test/src/main/java/org/codehaus/mojo/versions/utils/MockUtils.java
@@ -152,11 +152,22 @@ public static RepositorySystem mockRepositorySystem() {
* @return mocked {@link MavenSession}
*/
public static MavenSession mockMavenSession() {
+ MavenProject project = mock(MavenProject.class);
+ when(project.getRemotePluginRepositories()).thenReturn(emptyList());
+ when(project.getRemoteProjectRepositories()).thenReturn(emptyList());
+ return mockMavenSession(project);
+ }
+
+ /**
+ * Creates a very simple mock of {@link MavenSession}
+ * by providing only a non-{@code null} implementation of its {@link MavenSession#getRepositorySession()} method.
+ * @param project {@link MavenProject} to link to
+ * @return mocked {@link MavenSession}
+ */
+ public static MavenSession mockMavenSession(MavenProject project) {
MavenSession session = mock(MavenSession.class);
when(session.getRepositorySession()).thenReturn(mock(RepositorySystemSession.class));
- when(session.getCurrentProject()).thenReturn(mock(MavenProject.class));
- when(session.getCurrentProject().getRemotePluginRepositories()).thenReturn(emptyList());
- when(session.getCurrentProject().getRemoteProjectRepositories()).thenReturn(emptyList());
+ when(session.getCurrentProject()).thenReturn(project);
return session;
}
}