diff --git a/pom.xml b/pom.xml
index f37b1185c8..a5d28780b0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -180,6 +180,11 @@
maven-compat${maven.version}
+
+ org.apache.maven
+ maven-embedder
+ 3.3.9
+ org.apache.maven.enforcer
diff --git a/versions-common/pom.xml b/versions-common/pom.xml
index c102c28462..b5893179b6 100644
--- a/versions-common/pom.xml
+++ b/versions-common/pom.xml
@@ -50,6 +50,10 @@
org.apache.mavenmaven-settings
+
+ org.apache.maven
+ maven-embedder
+ com.fasterxml.woodstoxwoodstox-core
@@ -112,6 +116,26 @@
org.eclipse.sisusisu-maven-plugin
+
+ org.codehaus.modello
+ modello-maven-plugin
+
+
+ src/main/mdo/core-extensions.mdo
+
+ 1.1.0
+
+
+
+ generate-java-classes
+
+ xpp3-reader
+ java
+
+ generate-sources
+
+
+
diff --git a/versions-common/src/main/java/org/codehaus/mojo/versions/api/DefaultVersionsHelper.java b/versions-common/src/main/java/org/codehaus/mojo/versions/api/DefaultVersionsHelper.java
index d3844285c1..9a75757bf4 100644
--- a/versions-common/src/main/java/org/codehaus/mojo/versions/api/DefaultVersionsHelper.java
+++ b/versions-common/src/main/java/org/codehaus/mojo/versions/api/DefaultVersionsHelper.java
@@ -1,22 +1,18 @@
package org.codehaus.mojo.versions.api;
/*
- * 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
+ * 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
+ * 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.
+ * 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.BufferedInputStream;
@@ -45,6 +41,7 @@
import java.util.concurrent.Future;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
@@ -89,6 +86,7 @@
import org.eclipse.aether.resolution.VersionRangeRequest;
import org.eclipse.aether.resolution.VersionRangeResolutionException;
+import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Optional.empty;
import static java.util.Optional.of;
@@ -172,12 +170,35 @@ public Log getLog() {
public ArtifactVersions lookupArtifactVersions(
Artifact artifact, VersionRange versionRange, boolean usePluginRepositories)
throws VersionRetrievalException {
+ return lookupArtifactVersions(artifact, versionRange, usePluginRepositories, !usePluginRepositories);
+ }
+
+ @Override
+ public ArtifactVersions lookupArtifactVersions(
+ Artifact artifact, VersionRange versionRange, boolean usePluginRepositories, boolean useProjectRepositories)
+ throws VersionRetrievalException {
try {
Collection ignoredVersions = getIgnoredVersions(artifact);
if (!ignoredVersions.isEmpty() && getLog().isDebugEnabled()) {
getLog().debug("Found ignored versions: "
+ ignoredVersions.stream().map(IgnoreVersion::toString).collect(Collectors.joining(", ")));
}
+
+ final List repositories;
+ if (usePluginRepositories && !useProjectRepositories) {
+ repositories = mavenSession.getCurrentProject().getRemotePluginRepositories();
+ } else if (!usePluginRepositories && useProjectRepositories) {
+ repositories = mavenSession.getCurrentProject().getRemoteProjectRepositories();
+ } else if (usePluginRepositories) {
+ repositories = Stream.concat(
+ mavenSession.getCurrentProject().getRemoteProjectRepositories().stream(),
+ mavenSession.getCurrentProject().getRemotePluginRepositories().stream())
+ .distinct()
+ .collect(Collectors.toList());
+ } else {
+ // testing?
+ repositories = emptyList();
+ }
return new ArtifactVersions(
artifact,
aetherRepositorySystem
@@ -191,13 +212,7 @@ public ArtifactVersions lookupArtifactVersions(
.findFirst()
.map(Restriction::toString))
.orElse("(,)")),
- usePluginRepositories
- ? mavenSession
- .getCurrentProject()
- .getRemotePluginRepositories()
- : mavenSession
- .getCurrentProject()
- .getRemoteProjectRepositories(),
+ repositories,
"lookupArtifactVersions"))
.getVersions()
.stream()
@@ -428,16 +443,20 @@ public ArtifactVersion createArtifactVersion(String version) {
return DefaultArtifactVersionCache.of(version);
}
- @Override
public Map lookupDependenciesUpdates(
- Set dependencies, boolean usePluginRepositories, boolean allowSnapshots)
+ Set dependencies,
+ boolean usePluginRepositories,
+ boolean useProjectRepositories,
+ boolean allowSnapshots)
throws VersionRetrievalException {
ExecutorService executor = Executors.newFixedThreadPool(LOOKUP_PARALLEL_THREADS);
try {
Map dependencyUpdates = new TreeMap<>(DependencyComparator.INSTANCE);
List>> futures = dependencies.stream()
.map(dependency -> executor.submit(() -> new ImmutablePair<>(
- dependency, lookupDependencyUpdates(dependency, usePluginRepositories, allowSnapshots))))
+ dependency,
+ lookupDependencyUpdates(
+ dependency, usePluginRepositories, useProjectRepositories, allowSnapshots))))
.collect(Collectors.toList());
for (Future extends Pair> details : futures) {
Pair pair = details.get();
@@ -453,12 +472,22 @@ dependency, lookupDependencyUpdates(dependency, usePluginRepositories, allowSnap
}
}
+ @Override
+ public Map lookupDependenciesUpdates(
+ Set dependencies, boolean usePluginRepositories, boolean allowSnapshots)
+ throws VersionRetrievalException {
+ return lookupDependenciesUpdates(dependencies, usePluginRepositories, !usePluginRepositories, allowSnapshots);
+ }
+
@Override
public ArtifactVersions lookupDependencyUpdates(
- Dependency dependency, boolean usePluginRepositories, boolean allowSnapshots)
+ Dependency dependency,
+ boolean usePluginRepositories,
+ boolean useProjectRepositories,
+ boolean allowSnapshots)
throws VersionRetrievalException {
- ArtifactVersions allVersions =
- lookupArtifactVersions(createDependencyArtifact(dependency), usePluginRepositories);
+ ArtifactVersions allVersions = lookupArtifactVersions(
+ createDependencyArtifact(dependency), null, usePluginRepositories, useProjectRepositories);
return new ArtifactVersions(
allVersions.getArtifact(),
Arrays.stream(allVersions.getAllUpdates(allowSnapshots)).collect(Collectors.toList()),
diff --git a/versions-common/src/main/java/org/codehaus/mojo/versions/api/VersionsHelper.java b/versions-common/src/main/java/org/codehaus/mojo/versions/api/VersionsHelper.java
index d10b4e0d92..0c3e2347e0 100644
--- a/versions-common/src/main/java/org/codehaus/mojo/versions/api/VersionsHelper.java
+++ b/versions-common/src/main/java/org/codehaus/mojo/versions/api/VersionsHelper.java
@@ -1,22 +1,18 @@
package org.codehaus.mojo.versions.api;
/*
- * 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
+ * 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
+ * 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.
+ * 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.util.Collection;
@@ -152,7 +148,7 @@ Artifact createDependencyArtifact(
* The resulting {@link ArtifactVersions} instance will contain all versions, including snapshots.
*
* @param artifact The artifact to look for versions of.
- * @param usePluginRepositories true will consult the pluginRepositories, while false will
+ * @param usePluginRepositories {@code true} will consult the pluginRepositories, while {@code false} will
* consult the repositories for normal dependencies.
* @return The details of the available artifact versions.
* @throws VersionRetrievalException thrown if version resolution fails
@@ -167,7 +163,24 @@ ArtifactVersions lookupArtifactVersions(Artifact artifact, boolean usePluginRepo
* The resulting {@link ArtifactVersions} instance will contain all versions, including snapshots.
*
* @param artifact The artifact to look for versions of.
- * @param versionRange versionRange to restrict the search
+ * @param versionRange versionRange to restrict the search, may be {@code null}
+ * @param usePluginRepositories {@code true} will consult the pluginRepositories
+ * @param useProjectRepositories {@code true} will consult regular project repositories
+ * @return The details of the available artifact versions.
+ * @throws VersionRetrievalException thrown if version resolution fails
+ * @since 2.15.0
+ */
+ ArtifactVersions lookupArtifactVersions(
+ Artifact artifact, VersionRange versionRange, boolean usePluginRepositories, boolean useProjectRepositories)
+ throws VersionRetrievalException;
+
+ /**
+ * Looks up the versions of the specified artifact that are available in either the local repository, or the
+ * appropriate remote repositories.
+ * The resulting {@link ArtifactVersions} instance will contain all versions, including snapshots.
+ *
+ * @param artifact The artifact to look for versions of.
+ * @param versionRange versionRange to restrict the search, may be {@code null}
* @param usePluginRepositories true will consult the pluginRepositories, while false will
* consult the repositories for normal dependencies.
* @return The details of the available artifact versions.
@@ -190,18 +203,39 @@ Map lookupDependenciesUpdates(
Set dependencies, boolean usePluginRepositories, boolean allowSnapshots)
throws VersionRetrievalException;
+ /**
+ * Returns a map of all possible updates per dependency. The lookup is done in parallel using
+ * {@code LOOKUP_PARALLEL_THREADS} threads.
+ *
+ * @param dependencies The set of {@link Dependency} instances to look up.
+ * @param usePluginRepositories Search the plugin repositories.
+ * @param useProjectRepositories whether to use regular project repositories
+ * @param allowSnapshots whether snapshots should be included
+ * @return map containing the ArtifactVersions object per dependency
+ */
+ Map lookupDependenciesUpdates(
+ Set dependencies,
+ boolean usePluginRepositories,
+ boolean useProjectRepositories,
+ boolean allowSnapshots)
+ throws VersionRetrievalException;
+
/**
* Creates an {@link org.codehaus.mojo.versions.api.ArtifactVersions} instance from a dependency.
*
* @param dependency The dependency.
* @param usePluginRepositories Search the plugin repositories.
+ * @param useProjectRepositories whether to use regular project repositories
* @param allowSnapshots whether snapshots should be included
* @return The details of updates to the dependency.
* @throws VersionRetrievalException thrown if version resolution fails
* @since 1.0-beta-1
*/
ArtifactVersions lookupDependencyUpdates(
- Dependency dependency, boolean usePluginRepositories, boolean allowSnapshots)
+ Dependency dependency,
+ boolean usePluginRepositories,
+ boolean useProjectRepositories,
+ boolean allowSnapshots)
throws VersionRetrievalException;
/**
diff --git a/versions-common/src/main/java/org/codehaus/mojo/versions/filtering/DependencyFilter.java b/versions-common/src/main/java/org/codehaus/mojo/versions/filtering/DependencyFilter.java
index 4af2a4fbfa..d2808a0a76 100644
--- a/versions-common/src/main/java/org/codehaus/mojo/versions/filtering/DependencyFilter.java
+++ b/versions-common/src/main/java/org/codehaus/mojo/versions/filtering/DependencyFilter.java
@@ -1,22 +1,18 @@
package org.codehaus.mojo.versions.filtering;
/*
- * 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
+ * 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
+ * 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.
+ * 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.util.Collection;
@@ -66,10 +62,14 @@ public Set removingFrom(Collection dependencies) {
return filterBy(dependencies, not(this::matchersMatch));
}
- private boolean matchersMatch(Dependency dependency) {
+ public boolean matchersMatch(Dependency dependency) {
return matchers.stream().anyMatch(m -> m.test(dependency));
}
+ public boolean matchersDontMatch(Dependency dependency) {
+ return !matchersMatch(dependency);
+ }
+
private TreeSet filterBy(Collection dependencies, Predicate predicate) {
return dependencies.stream()
.filter(predicate)
diff --git a/versions-common/src/main/java/org/codehaus/mojo/versions/utils/CoreExtensionUtils.java b/versions-common/src/main/java/org/codehaus/mojo/versions/utils/CoreExtensionUtils.java
new file mode 100644
index 0000000000..64564dfe56
--- /dev/null
+++ b/versions-common/src/main/java/org/codehaus/mojo/versions/utils/CoreExtensionUtils.java
@@ -0,0 +1,64 @@
+package org.codehaus.mojo.versions.utils;
+
+/*
+ * 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.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.stream.Stream;
+
+import org.apache.maven.model.Extension;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.mojo.versions.model.io.xpp3.CoreExtensionsXpp3Reader;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+
+/**
+ * Utilities for reading and handling core extensions.
+ *
+ * @author Andrzej Jarmoniuk
+ * @since 2.15.0
+ */
+public final class CoreExtensionUtils {
+ /**
+ * Reads the core extensions (not build extensions) configured for the given project
+ * from the {@code ${project}/.mvn/extensions.xml} file.
+ *
+ * @param project {@link MavenProject} instance
+ * @return stream of core extensions defined in the {@code ${project}/.mvn/extensions.xml} file
+ * @throws IOException thrown if a file I/O operation fails
+ * @throws XmlPullParserException thrown if the file cannot be parsed
+ * @since 2.15.0
+ */
+ public static Stream getCoreExtensions(MavenProject project) throws IOException, XmlPullParserException {
+ Path extensionsFile = project.getBasedir().toPath().resolve(".mvn/extensions.xml");
+ if (!Files.isRegularFile(extensionsFile)) {
+ return Stream.empty();
+ }
+
+ try (Reader reader = new BufferedReader(new InputStreamReader(Files.newInputStream(extensionsFile)))) {
+ return new CoreExtensionsXpp3Reader()
+ .read(reader).getExtensions().stream().map(ex -> ExtensionBuilder.newBuilder()
+ .withGroupId(ex.getGroupId())
+ .withArtifactId(ex.getArtifactId())
+ .withVersion(ex.getVersion())
+ .build());
+ }
+ }
+}
diff --git a/versions-common/src/main/java/org/codehaus/mojo/versions/utils/DependencyBuilder.java b/versions-common/src/main/java/org/codehaus/mojo/versions/utils/DependencyBuilder.java
index 58ca87c2ff..6574f4b557 100644
--- a/versions-common/src/main/java/org/codehaus/mojo/versions/utils/DependencyBuilder.java
+++ b/versions-common/src/main/java/org/codehaus/mojo/versions/utils/DependencyBuilder.java
@@ -1,24 +1,20 @@
+package org.codehaus.mojo.versions.utils;
+
/*
- * 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
+ * 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
+ * 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.
+ * 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.codehaus.mojo.versions.utils;
-
import java.util.Optional;
import org.apache.maven.model.Dependency;
@@ -136,7 +132,9 @@ public static DependencyBuilder newBuilder() {
* @param artifactId artifactId of the dependency
* @param version version of the dependency
* @return new instance of {@linkplain Dependency}
+ * @deprecated please use the {@link #newBuilder()} method instead
*/
+ @Deprecated
public static Dependency dependencyWith(String groupId, String artifactId, String version) {
return newBuilder()
.withGroupId(groupId)
@@ -154,7 +152,9 @@ public static Dependency dependencyWith(String groupId, String artifactId, Strin
* @param classifier classifier of the dependency
* @param scope scope of the dependency
* @return new instance of {@linkplain Dependency}
+ * @deprecated please use the {@link #newBuilder()} method instead
*/
+ @Deprecated
public static Dependency dependencyWith(
String groupId, String artifactId, String version, String type, String classifier, String scope) {
return newBuilder()
diff --git a/versions-common/src/main/java/org/codehaus/mojo/versions/utils/ExtensionBuilder.java b/versions-common/src/main/java/org/codehaus/mojo/versions/utils/ExtensionBuilder.java
new file mode 100644
index 0000000000..7d763606d2
--- /dev/null
+++ b/versions-common/src/main/java/org/codehaus/mojo/versions/utils/ExtensionBuilder.java
@@ -0,0 +1,103 @@
+package org.codehaus.mojo.versions.utils;
+
+/*
+ * 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.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.Extension;
+import org.apache.maven.model.InputLocation;
+
+import static java.util.Optional.empty;
+import static java.util.Optional.ofNullable;
+
+/**
+ * Builder class for {@linkplain Extension}
+ */
+@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+public class ExtensionBuilder {
+ private Optional groupId = empty();
+ private Optional artifactId = empty();
+ private Optional version = empty();
+ private Map
+
The default location for the Core Extensions descriptor file is ${maven.projectBasedir}/.mvn/extensions.xml
+ ]]>
+
+
+
+ package
+ org.codehaus.mojo.versions.model
+
+
+
+
+
+ CoreExtensions
+ Extensions to load.
+ 1.0.0+
+
+
+ extensions
+ A set of build extensions to use from this project.
+ 1.0.0+
+
+ CoreExtension
+ *
+
+
+
+
+
+ CoreExtension
+ Describes a build extension to utilise.
+ 1.0.0+
+
+
+ groupId
+ The group ID of the extension's artifact.
+ 1.0.0+
+ true
+ String
+
+
+ artifactId
+ The artifact ID of the extension.
+ 1.0.0+
+ true
+ String
+
+
+ version
+ The version of the extension.
+ 1.0.0+
+ true
+ String
+
+
+ classLoadingStrategy
+ The class loading strategy: 'self-first' (the default), 'parent-first' (loads classes from the parent, then from the extension) or 'plugin' (follows the rules from extensions defined as plugins).
+ 1.1.0+
+ self-first
+ false
+ String
+
+
+
+
+ 1.0.0+
+
+ ::}, never {@code null}.
+ */
+ public String getId()
+ {
+ StringBuilder id = new StringBuilder( 128 );
+
+ id.append( ( getGroupId() == null ) ? "[unknown-group-id]" : getGroupId() );
+ id.append( ":" );
+ id.append( ( getArtifactId() == null ) ? "[unknown-artifact-id]" : getArtifactId() );
+ id.append( ":" );
+ id.append( ( getVersion() == null ) ? "[unknown-version]" : getVersion() );
+
+ return id.toString();
+ }
+ ]]>
+
+
+
+
+
+
diff --git a/versions-common/src/test/java/org/codehaus/mojo/versions/utils/CoreExtensionUtilsTest.java b/versions-common/src/test/java/org/codehaus/mojo/versions/utils/CoreExtensionUtilsTest.java
new file mode 100644
index 0000000000..90aaeec527
--- /dev/null
+++ b/versions-common/src/test/java/org/codehaus/mojo/versions/utils/CoreExtensionUtilsTest.java
@@ -0,0 +1,77 @@
+package org.codehaus.mojo.versions.utils;
+/*
+ * 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.File;
+import java.io.IOException;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.model.Extension;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+import org.junit.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasItems;
+import static org.hamcrest.Matchers.is;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Unit tests for {@link CoreExtensionUtils}
+ *
+ * @author Andrzej Jarmoniuk
+ */
+public class CoreExtensionUtilsTest {
+
+ @Test
+ public void testNoExtensions() throws XmlPullParserException, IOException {
+ MavenProject project = mock(MavenProject.class);
+ when(project.getBasedir())
+ .thenReturn(
+ new File("src/test/resources/org/codehaus/mojo/versions/utils/core-extensions/no-extensions"));
+ MavenSession session = mock(MavenSession.class);
+ when(session.getCurrentProject()).thenReturn(project);
+ assertThat(CoreExtensionUtils.getCoreExtensions(project).findAny(), is(Optional.empty()));
+ }
+
+ @Test
+ public void testExtensionsFound() throws XmlPullParserException, IOException {
+ MavenProject project = mock(MavenProject.class);
+ when(project.getBasedir())
+ .thenReturn(new File("src/test/resources/org/codehaus/mojo/versions/utils/core-extensions"));
+ MavenSession session = mock(MavenSession.class);
+ when(session.getCurrentProject()).thenReturn(project);
+ Set extensions =
+ CoreExtensionUtils.getCoreExtensions(project).collect(Collectors.toSet());
+ assertThat(
+ extensions,
+ hasItems(
+ ExtensionBuilder.newBuilder()
+ .withGroupId("default-group")
+ .withArtifactId("artifactA")
+ .withVersion("1.0.0")
+ .build(),
+ ExtensionBuilder.newBuilder()
+ .withGroupId("default-group")
+ .withArtifactId("artifactB")
+ .withVersion("2.0.0")
+ .build()));
+ }
+}
diff --git a/versions-common/src/test/resources/org/codehaus/mojo/versions/utils/core-extensions/.mvn/extensions.xml b/versions-common/src/test/resources/org/codehaus/mojo/versions/utils/core-extensions/.mvn/extensions.xml
new file mode 100644
index 0000000000..172cdbc1f9
--- /dev/null
+++ b/versions-common/src/test/resources/org/codehaus/mojo/versions/utils/core-extensions/.mvn/extensions.xml
@@ -0,0 +1,13 @@
+
+
+ default-group
+ artifactA
+ 1.0.0
+
+
+ default-group
+ artifactB
+ 2.0.0
+
+
\ No newline at end of file
diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-001/invoker.properties b/versions-maven-plugin/src/it/it-display-extension-updates-001/invoker.properties
new file mode 100644
index 0000000000..5da846218c
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-display-extension-updates-001/invoker.properties
@@ -0,0 +1,8 @@
+invoker.goals.1 = ${project.groupId}:${project.artifactId}:${project.version}:display-extension-updates
+invoker.mavenOpts.1 = -Dversions.outputFile=./output1.txt -DoutputEncoding=UTF-8
+
+invoker.goals.2 = ${project.groupId}:${project.artifactId}:${project.version}:display-extension-updates
+invoker.mavenOpts.2 = -Dversions.outputFile=./output2.txt -DoutputEncoding=UTF-8 -DextensionExcludes=localhost
+
+invoker.goals.3 = ${project.groupId}:${project.artifactId}:${project.version}:display-extension-updates
+invoker.mavenOpts.3 = -Dversions.outputFile=./output3.txt -DoutputEncoding=UTF-8 -DextensionIncludes=localhost -DextensionExcludes=localhost:dummy-api
diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-001/pom.xml b/versions-maven-plugin/src/it/it-display-extension-updates-001/pom.xml
new file mode 100644
index 0000000000..fe8f43302d
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-display-extension-updates-001/pom.xml
@@ -0,0 +1,25 @@
+
+ 4.0.0
+ localhost
+ it-display-extension-updates-001
+
+ 1.0
+ pom
+
+
+
+
+ localhost
+ dummy-maven-plugin
+ 1.0
+
+
+ localhost
+ dummy-api
+ 1.0
+
+
+
+
diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-001/verify.groovy b/versions-maven-plugin/src/it/it-display-extension-updates-001/verify.groovy
new file mode 100644
index 0000000000..4f5fe3ab69
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-display-extension-updates-001/verify.groovy
@@ -0,0 +1,10 @@
+def output1 = new File( basedir, "output1.txt").text
+assert output1 =~ /\Qlocalhost:dummy-maven-plugin\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.1\E/
+assert output1 =~ /\Qlocalhost:dummy-api\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.0\E/
+
+def output2 = new File( basedir, "output2.txt")
+assert !output2.exists()
+
+def output3 = new File( basedir, "output3.txt").text
+assert output3 =~ /\Qlocalhost:dummy-maven-plugin\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.1\E/
+assert !( output3 =~ /\Qlocalhost:dummy-api\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.0\E/ )
diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-002/.mvn/extensions.xml b/versions-maven-plugin/src/it/it-display-extension-updates-002/.mvn/extensions.xml
new file mode 100644
index 0000000000..1f4f4fbac7
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-display-extension-updates-002/.mvn/extensions.xml
@@ -0,0 +1,13 @@
+
+
+ localhost
+ dummy-maven-plugin
+ 1.0
+
+
+ localhost
+ dummy-api
+ 1.0
+
+
\ No newline at end of file
diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-002/invoker.properties b/versions-maven-plugin/src/it/it-display-extension-updates-002/invoker.properties
new file mode 100644
index 0000000000..5da846218c
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-display-extension-updates-002/invoker.properties
@@ -0,0 +1,8 @@
+invoker.goals.1 = ${project.groupId}:${project.artifactId}:${project.version}:display-extension-updates
+invoker.mavenOpts.1 = -Dversions.outputFile=./output1.txt -DoutputEncoding=UTF-8
+
+invoker.goals.2 = ${project.groupId}:${project.artifactId}:${project.version}:display-extension-updates
+invoker.mavenOpts.2 = -Dversions.outputFile=./output2.txt -DoutputEncoding=UTF-8 -DextensionExcludes=localhost
+
+invoker.goals.3 = ${project.groupId}:${project.artifactId}:${project.version}:display-extension-updates
+invoker.mavenOpts.3 = -Dversions.outputFile=./output3.txt -DoutputEncoding=UTF-8 -DextensionIncludes=localhost -DextensionExcludes=localhost:dummy-api
diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-002/pom.xml b/versions-maven-plugin/src/it/it-display-extension-updates-002/pom.xml
new file mode 100644
index 0000000000..28820da695
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-display-extension-updates-002/pom.xml
@@ -0,0 +1,11 @@
+
+ 4.0.0
+ localhost
+ it-display-extension-updates-002
+
+ 1.0
+ pom
+
+
diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-002/verify.groovy b/versions-maven-plugin/src/it/it-display-extension-updates-002/verify.groovy
new file mode 100644
index 0000000000..4f5fe3ab69
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-display-extension-updates-002/verify.groovy
@@ -0,0 +1,10 @@
+def output1 = new File( basedir, "output1.txt").text
+assert output1 =~ /\Qlocalhost:dummy-maven-plugin\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.1\E/
+assert output1 =~ /\Qlocalhost:dummy-api\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.0\E/
+
+def output2 = new File( basedir, "output2.txt")
+assert !output2.exists()
+
+def output3 = new File( basedir, "output3.txt").text
+assert output3 =~ /\Qlocalhost:dummy-maven-plugin\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.1\E/
+assert !( output3 =~ /\Qlocalhost:dummy-api\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.0\E/ )
diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-003/.mvn/extensions.xml b/versions-maven-plugin/src/it/it-display-extension-updates-003/.mvn/extensions.xml
new file mode 100644
index 0000000000..18a2a6a1d2
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-display-extension-updates-003/.mvn/extensions.xml
@@ -0,0 +1,8 @@
+
+
+ localhost
+ dummy-api
+ 1.0
+
+
\ No newline at end of file
diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-003/invoker.properties b/versions-maven-plugin/src/it/it-display-extension-updates-003/invoker.properties
new file mode 100644
index 0000000000..0bd6e32e03
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-display-extension-updates-003/invoker.properties
@@ -0,0 +1,5 @@
+invoker.goals.1 = ${project.groupId}:${project.artifactId}:${project.version}:display-extension-updates
+invoker.mavenOpts.1 = -DprocessBuildExtensions=false -Dversions.outputFile=./output1.txt -DoutputEncoding=UTF-8
+
+invoker.goals.2 = ${project.groupId}:${project.artifactId}:${project.version}:display-extension-updates
+invoker.mavenOpts.2 = -DprocessCoreExtensions=false -Dversions.outputFile=./output2.txt -DoutputEncoding=UTF-8
diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-003/pom.xml b/versions-maven-plugin/src/it/it-display-extension-updates-003/pom.xml
new file mode 100644
index 0000000000..655a5ea9e1
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-display-extension-updates-003/pom.xml
@@ -0,0 +1,21 @@
+
+ 4.0.0
+ localhost
+ it-display-extension-updates-003
+
+ 1.0
+ pom
+
+
+
+
+ localhost
+ dummy-maven-plugin
+ 1.0
+
+
+
+
+
diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-003/verify.groovy b/versions-maven-plugin/src/it/it-display-extension-updates-003/verify.groovy
new file mode 100644
index 0000000000..f2e88457db
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-display-extension-updates-003/verify.groovy
@@ -0,0 +1,7 @@
+def output1 = new File( basedir, "output1.txt").text
+assert !( output1 =~ /\Qlocalhost:dummy-maven-plugin\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.1\E/ )
+assert output1 =~ /\Qlocalhost:dummy-api\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.0\E/
+
+def output2 = new File( basedir, "output2.txt").text
+assert output2 =~ /\Qlocalhost:dummy-maven-plugin\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.1\E/
+assert !( output2 =~ /\Qlocalhost:dummy-api\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.0\E/ )
diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-004/child/.mvn/extensions.xml b/versions-maven-plugin/src/it/it-display-extension-updates-004/child/.mvn/extensions.xml
new file mode 100644
index 0000000000..4a58d492bd
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-display-extension-updates-004/child/.mvn/extensions.xml
@@ -0,0 +1,8 @@
+
+
+ localhost
+ dummy-impl
+ 1.0
+
+
\ No newline at end of file
diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-004/child/pom.xml b/versions-maven-plugin/src/it/it-display-extension-updates-004/child/pom.xml
new file mode 100644
index 0000000000..ff98b042b1
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-display-extension-updates-004/child/pom.xml
@@ -0,0 +1,31 @@
+
+ 4.0.0
+
+
+ localhost
+ it-display-extension-updates-004
+ 1.0
+
+
+ child
+ 1.0
+
+ pom
+
+
+ 1.0
+
+
+
+
+
+ localhost
+ dummy-maven-plugin
+ ${dummyMavenPluginVersion}
+
+
+
+
+
diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-004/invoker.properties b/versions-maven-plugin/src/it/it-display-extension-updates-004/invoker.properties
new file mode 100644
index 0000000000..bb83305ac1
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-display-extension-updates-004/invoker.properties
@@ -0,0 +1,5 @@
+invoker.goals.1 = ${project.groupId}:${project.artifactId}:${project.version}:display-extension-updates
+invoker.mavenOpts.1 = -Dversions.outputFile=./output1.txt -DoutputEncoding=UTF-8
+
+invoker.goals.2 = ${project.groupId}:${project.artifactId}:${project.version}:display-extension-updates
+invoker.mavenOpts.2 = -DinterpolateProperties=false -Dversions.outputFile=./output2.txt -DoutputEncoding=UTF-8
diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-004/pom.xml b/versions-maven-plugin/src/it/it-display-extension-updates-004/pom.xml
new file mode 100644
index 0000000000..8e3cd5c1fa
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-display-extension-updates-004/pom.xml
@@ -0,0 +1,29 @@
+
+ 4.0.0
+ localhost
+ it-display-extension-updates-004
+ 1.0
+ pom
+
+
+ child
+
+
+
+ 1.0
+ 2.0
+
+
+
+
+
+ localhost
+ dummy-api
+ ${dummyApiVersion}
+
+
+
+
+
diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-004/verify.groovy b/versions-maven-plugin/src/it/it-display-extension-updates-004/verify.groovy
new file mode 100644
index 0000000000..3c2f9333c3
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-display-extension-updates-004/verify.groovy
@@ -0,0 +1,11 @@
+def parentOutput = new File( basedir, "output1.txt").text
+assert parentOutput =~ /\Qlocalhost:dummy-maven-plugin\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.1\E/
+assert parentOutput =~ /\Qlocalhost:dummy-api\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.0\E/
+assert !(parentOutput =~ /\Qlocalhost:dummy-impl\E/)
+
+def childOutput = new File( basedir, "child/output1.txt")
+assert !childOutput.exists()
+
+def parentOutput2 = new File( basedir, "output2.txt").text
+assert parentOutput2 =~ /\Qlocalhost:dummy-maven-plugin\E\s*\.*\s*\Q\u0024{dummyMavenPluginVersion}\E\s+->\s+\Q3.1\E/
+assert parentOutput2 =~ /\Qlocalhost:dummy-api\E\s*\.*\s*\Q\u0024{dummyApiVersion}\E\s+->\s+\Q3.0\E/
diff --git a/versions-maven-plugin/src/main/java/org/codehaus/mojo/versions/DisplayExtensionUpdatesMojo.java b/versions-maven-plugin/src/main/java/org/codehaus/mojo/versions/DisplayExtensionUpdatesMojo.java
new file mode 100644
index 0000000000..1c9b51a183
--- /dev/null
+++ b/versions-maven-plugin/src/main/java/org/codehaus/mojo/versions/DisplayExtensionUpdatesMojo.java
@@ -0,0 +1,362 @@
+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 javax.inject.Inject;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.maven.artifact.ArtifactUtils;
+import org.apache.maven.artifact.versioning.ArtifactVersion;
+import org.apache.maven.model.Build;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.Extension;
+import org.apache.maven.model.Model;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.repository.RepositorySystem;
+import org.apache.maven.wagon.Wagon;
+import org.codehaus.mojo.versions.api.ArtifactVersions;
+import org.codehaus.mojo.versions.api.PomHelper;
+import org.codehaus.mojo.versions.api.Segment;
+import org.codehaus.mojo.versions.api.VersionRetrievalException;
+import org.codehaus.mojo.versions.api.recording.ChangeRecorder;
+import org.codehaus.mojo.versions.filtering.DependencyFilter;
+import org.codehaus.mojo.versions.filtering.WildcardMatcher;
+import org.codehaus.mojo.versions.rewriting.ModifiedPomXMLEventReader;
+import org.codehaus.mojo.versions.utils.CoreExtensionUtils;
+import org.codehaus.mojo.versions.utils.DependencyBuilder;
+import org.codehaus.mojo.versions.utils.ExtensionBuilder;
+import org.codehaus.mojo.versions.utils.ModelNode;
+import org.codehaus.mojo.versions.utils.SegmentUtils;
+import org.codehaus.plexus.util.StringUtils;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+
+import static java.util.Optional.of;
+import static org.codehaus.mojo.versions.api.Segment.MAJOR;
+
+/**
+ * Displays all build and core extensions that have newer versions available.
+ *
+ * @author Andrzej Jarmoniuk
+ * @since 2.15.0
+ */
+@Mojo(name = "display-extension-updates", aggregator = true, threadSafe = true)
+public class DisplayExtensionUpdatesMojo extends AbstractVersionsDisplayMojo {
+
+ // ------------------------------ FIELDS ------------------------------
+
+ /**
+ * The width to pad info messages.
+ *
+ * @since 1.0-alpha-1
+ */
+ private static final int INFO_PAD_SIZE = 72;
+
+ /**
+ *
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.
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}