org.codehaus.mojo.versions.DisplayPluginUpdatesMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of versions-maven-plugin Show documentation
Show all versions of versions-maven-plugin Show documentation
Versions Plugin for Maven. The Versions Plugin updates the versions of components in the POM.
The newest version!
package org.codehaus.mojo.versions;
/*
* 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.
*/
import javax.inject.Inject;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.TransformerException;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.lifecycle.LifecycleExecutor;
import org.apache.maven.model.BuildBase;
import org.apache.maven.model.Model;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.PluginManagement;
import org.apache.maven.model.Prerequisites;
import org.apache.maven.model.Profile;
import org.apache.maven.model.ReportPlugin;
import org.apache.maven.model.Reporting;
import org.apache.maven.model.building.DefaultModelBuildingRequest;
import org.apache.maven.model.building.ModelBuildingRequest;
import org.apache.maven.model.building.ModelProblemCollector;
import org.apache.maven.model.building.ModelProblemCollectorRequest;
import org.apache.maven.model.interpolation.ModelInterpolator;
import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
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.project.MavenProject;
import org.apache.maven.project.ProjectBuilder;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.ProjectBuildingResult;
import org.apache.maven.rtinfo.RuntimeInformation;
import org.apache.maven.wagon.Wagon;
import org.codehaus.mojo.versions.api.PomHelper;
import org.codehaus.mojo.versions.api.VersionRetrievalException;
import org.codehaus.mojo.versions.api.recording.ChangeRecorder;
import org.codehaus.mojo.versions.ordering.MavenVersionComparator;
import org.codehaus.mojo.versions.rewriting.MutableXMLStreamReader;
import org.codehaus.mojo.versions.utils.DefaultArtifactVersionCache;
import org.codehaus.mojo.versions.utils.DependencyBuilder;
import org.codehaus.mojo.versions.utils.PluginComparator;
import org.eclipse.aether.RepositorySystem;
import static java.util.Collections.emptyMap;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toMap;
import static javax.xml.stream.XMLStreamConstants.END_DOCUMENT;
/**
* Displays all plugins that have newer versions available, taking care of Maven version prerequisites.
*
* @author Stephen Connolly
* @since 1.0-alpha-1
*/
@Mojo(name = "display-plugin-updates", threadSafe = true)
public class DisplayPluginUpdatesMojo extends AbstractVersionsDisplayMojo {
// ------------------------------ FIELDS ------------------------------
/**
* The width to pad warn messages.
*
* @since 1.0-alpha-1
*/
private static final int WARN_PAD_SIZE = 65;
/**
* The width to pad info messages.
*
* @since 1.0-alpha-1
*/
private static final int INFO_PAD_SIZE = 68;
/**
* String to flag a plugin version being forced by the super-pom.
*
* @since 1.0-alpha-1
*/
private static final String FROM_SUPER_POM = "(from super-pom) ";
public static final Pattern PATTERN_PROJECT_PLUGIN = Pattern.compile(
"/project(/profiles/profile)?" + "((/build(/pluginManagement)?)|(/reporting))" + "/plugins/plugin");
public static final String SUPERPOM_PATH = "org/apache/maven/model/pom-4.0.0.xml";
/**
* @since 1.0-alpha-1
*/
private LifecycleExecutor lifecycleExecutor;
/**
* @since 1.0-alpha-3
*/
private ModelInterpolator modelInterpolator;
/**
* (Injected) instance of {@link RuntimeInformation}
*
* @since 2.14.0
*/
private final RuntimeInformation runtimeInformation;
/**
* The (injected) instance of {@link ProjectBuilder}
*
* @since 2.14.0
*/
protected final ProjectBuilder projectBuilder;
/**
* If set to {@code true}, will also display updates to plugins where no version is specified
* in the current POM, but whose version is specified in the parent or the "superpom".
* It might not always be possible to update these plugins,
* thus the default value of this parameter is {@code false}
.
*
* @since 2.15.0
*/
@Parameter(property = "processUnboundPlugins", defaultValue = "false")
protected boolean processUnboundPlugins;
// --------------------- GETTER / SETTER METHODS ---------------------
@Inject
@SuppressWarnings("checkstyle:ParameterNumber")
public DisplayPluginUpdatesMojo(
ArtifactHandlerManager artifactHandlerManager,
RepositorySystem repositorySystem,
ProjectBuilder projectBuilder,
Map wagonMap,
LifecycleExecutor lifecycleExecutor,
ModelInterpolator modelInterpolator,
RuntimeInformation runtimeInformation,
Map changeRecorders) {
super(artifactHandlerManager, repositorySystem, wagonMap, changeRecorders);
this.projectBuilder = projectBuilder;
this.lifecycleExecutor = lifecycleExecutor;
this.modelInterpolator = modelInterpolator;
this.runtimeInformation = runtimeInformation;
}
/**
* Returns the pluginManagement section of the super-pom.
*
* @return Returns the pluginManagement section of the super-pom.
*/
private Map getSuperPomPluginManagement() {
// we need to provide a copy with the version blanked out so that inferring from super-pom
// works as for 2.x as 3.x fills in the version on us!
Map result =
lifecycleExecutor
.getPluginsBoundByDefaultToAllLifecycles(getProject().getPackaging())
.stream()
.collect(LinkedHashMap::new, (m, p) -> m.put(p.getKey(), p.getVersion()), Map::putAll);
try (InputStream superPomIs = getClass().getClassLoader().getResourceAsStream(SUPERPOM_PATH)) {
Objects.requireNonNull(superPomIs);
MutableXMLStreamReader pomReader = new MutableXMLStreamReader(superPomIs, Paths.get(SUPERPOM_PATH));
Stack pathStack = new Stack<>();
StackState curState = new StackState("");
for (int event = pomReader.getEventType();
event != END_DOCUMENT && pomReader.hasNext();
event = pomReader.next()) {
if (pomReader.isStartElement()) {
if (curState != null) {
String elementName = pomReader.getLocalName();
if (PATTERN_PROJECT_PLUGIN.matcher(curState.path).matches()) {
switch (elementName) {
case "groupId":
curState.groupId =
pomReader.getElementText().trim();
break;
case "artifactId":
curState.artifactId =
pomReader.getElementText().trim();
break;
case "version":
curState.version =
pomReader.getElementText().trim();
break;
default:
break;
}
}
pathStack.push(curState);
curState = new StackState(curState.path + "/" + elementName);
}
} else if (pomReader.isEndElement()) {
if (curState != null
&& curState.artifactId != null
&& PATTERN_PROJECT_PLUGIN.matcher(curState.path).matches()) {
result.putIfAbsent(
Plugin.constructKey(
curState.groupId == null
? PomHelper.APACHE_MAVEN_PLUGINS_GROUPID
: curState.groupId,
curState.artifactId),
curState.version);
}
curState = pathStack.pop();
}
}
} catch (IOException | XMLStreamException | TransformerException e) {
// ignore
}
return result;
}
/**
* Gets the plugin management plugins of a specific project.
*
* @param model the model to get the plugin management plugins from.
* @return The map of effective plugin versions keyed by coordinates.
* @since 1.0-alpha-1
*/
private Map getPluginManagement(Model model) {
// we want only those parts of pluginManagement that are defined in this project
Map pluginManagement = new HashMap<>();
try {
for (Plugin plugin : model.getBuild().getPluginManagement().getPlugins()) {
String coord = plugin.getKey();
String version = plugin.getVersion();
if (version != null) {
pluginManagement.put(coord, version);
}
}
} catch (NullPointerException e) {
// guess there are no plugins here
}
try {
for (Profile profile : model.getProfiles()) {
try {
for (Plugin plugin :
profile.getBuild().getPluginManagement().getPlugins()) {
String coord = plugin.getKey();
String version = plugin.getVersion();
if (version != null) {
pluginManagement.put(coord, version);
}
}
} catch (NullPointerException e) {
// guess there are no plugins here
}
}
} catch (NullPointerException e) {
// guess there are no profiles here
}
return pluginManagement;
}
// ------------------------ INTERFACE METHODS ------------------------
// --------------------- Interface Mojo ---------------------
/**
* @throws MojoExecutionException when things go wrong
* @throws MojoFailureException when things go wrong in a very bad way
* @see AbstractVersionsUpdaterMojo#execute()
* @since 1.0-alpha-1
*/
@SuppressWarnings("checkstyle:MethodLength")
public void execute() throws MojoExecutionException, MojoFailureException {
logInit();
Set pluginsWithVersionsSpecified;
try (MutableXMLStreamReader pomReader =
new MutableXMLStreamReader(getProject().getFile().toPath())) {
pluginsWithVersionsSpecified = findPluginsWithVersionsSpecified(pomReader);
} catch (XMLStreamException | IOException | TransformerException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
Map superPomPluginManagement = getSuperPomPluginManagement();
getLog().debug("superPom plugins = " + superPomPluginManagement);
List parents = getParentProjects(getProject());
Map parentPlugins = getParentsPlugins(parents);
// TODO remove, not used any more (found while extracting getParentsPlugins method and
// renaming parentPluginManagement to parentPlugins)
// NOTICE: getPluginManagementPlugins() takes profiles while getParentPlugins does not
// there is probably a little inconsistency (if plugins configured in profiles of parents)
Map parentBuildPlugins = new HashMap<>();
Map parentReportPlugins = new HashMap<>();
Set plugins = getPluginManagementPlugins(
superPomPluginManagement,
parentPlugins,
parentBuildPlugins,
parentReportPlugins,
pluginsWithVersionsSpecified);
List pluginUpdates = new ArrayList<>();
List pluginLockdowns = new ArrayList<>();
ArtifactVersion curMavenVersion = DefaultArtifactVersionCache.of(runtimeInformation.getMavenVersion());
ArtifactVersion specMavenVersion = MinimalMavenBuildVersionFinder.find(getProject(), getLog());
ArtifactVersion minMavenVersion = null;
boolean superPomDrivingMinVersion = false;
// if Maven prerequisite upgraded to a version, Map
Map> mavenUpgrades = new TreeMap<>(new MavenVersionComparator());
for (Plugin plugin : plugins) {
String coords = ArtifactUtils.versionlessKey(plugin.getGroupId(), plugin.getArtifactId());
String version = ofNullable(plugin.getVersion()).orElse(parentPlugins.get(coords));
boolean versionSpecifiedInCurrentPom = pluginsWithVersionsSpecified.contains(coords);
if (!versionSpecifiedInCurrentPom && !processUnboundPlugins && parentPlugins.containsKey(coords)) {
getLog().debug("Skip " + coords + ", version " + version + " is defined in parent POM.");
getLog().debug("Use the \"processUnboundPlugins\" parameter to see these updates.");
continue;
}
getLog().debug("Checking " + coords + " for updates newer than " + version);
String effectiveVersion;
ArtifactVersion artifactVersion;
try {
// now we want to find the newest versions and check their Maven version prerequisite
Pair effectiveVersionPair =
getEffectivePluginVersion(plugin, version, specMavenVersion, curMavenVersion, mavenUpgrades);
effectiveVersion = effectiveVersionPair.getRight();
artifactVersion = effectiveVersionPair.getLeft();
if (effectiveVersion != null) {
try {
ArtifactVersion requires = getPrerequisitesMavenVersion(
getPluginProject(plugin.getGroupId(), plugin.getArtifactId(), effectiveVersion));
if (minMavenVersion == null || compare(minMavenVersion, requires) < 0) {
minMavenVersion = requires;
}
} catch (ArtifactResolutionException | ProjectBuildingException e) {
// ignore bad version
}
}
} catch (VersionRetrievalException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
String newVersion;
if (version == null && versionSpecifiedInCurrentPom) {
// Hack ALERT!
//
// All this should be re-written in a less "pom is xml" way... but it'll
// work for now :-(
//
// we have removed the version information, as it was the same as from
// the super-pom... but it actually was specified.
version = artifactVersion != null ? artifactVersion.toString() : null;
}
if (getLog().isDebugEnabled()) {
getLog().debug("[" + coords + "].version=" + version);
getLog().debug("[" + coords + "].artifactVersion=" + artifactVersion);
getLog().debug("[" + coords + "].effectiveVersion=" + effectiveVersion);
getLog().debug("[" + coords + "].specified=" + versionSpecifiedInCurrentPom);
}
if (version == null || !processUnboundPlugins && !versionSpecifiedInCurrentPom) {
version = superPomPluginManagement.get(coords);
if (getLog().isDebugEnabled()) {
getLog().debug("[" + coords + "].superPom.version=" + version);
}
newVersion = artifactVersion != null
? artifactVersion.toString()
: (version != null ? version : (effectiveVersion != null ? effectiveVersion : "(unknown)"));
if (version != null) {
superPomDrivingMinVersion = true;
}
pluginLockdowns.add(pad(
compactKey(plugin.getGroupId(), plugin.getArtifactId()),
WARN_PAD_SIZE + getOutputLineWidthOffset(),
superPomDrivingMinVersion ? FROM_SUPER_POM : "",
newVersion));
} else if (artifactVersion != null) {
newVersion = artifactVersion.toString();
} else {
newVersion = null;
}
if (version != null
&& artifactVersion != null
&& newVersion != null
&& effectiveVersion != null
&& DefaultArtifactVersionCache.of(effectiveVersion)
.compareTo(DefaultArtifactVersionCache.of(newVersion))
< 0) {
pluginUpdates.add(pad(
compactKey(plugin.getGroupId(), plugin.getArtifactId()),
INFO_PAD_SIZE + getOutputLineWidthOffset(),
effectiveVersion,
" -> ",
newVersion));
}
}
// info on each plugin gathered: now it's time to display the result!
//
logLine(false, "");
// updates keeping currently defined Maven version minimum
if (pluginUpdates.isEmpty()) {
logLine(false, "All plugins with a version specified are using the latest versions.");
} else {
logLine(false, "The following plugin updates are available:");
for (String update : new TreeSet<>(pluginUpdates)) {
logLine(false, update);
}
}
logLine(false, "");
// has every plugin a specified version?
if (pluginLockdowns.isEmpty()) {
logLine(false, "All plugins have a version specified.");
} else {
getLog().warn("The following plugins do not have their version specified:");
for (String lockdown : new TreeSet<>(pluginLockdowns)) {
getLog().warn(lockdown);
}
}
logLine(false, "");
// information on minimum Maven version
if (specMavenVersion == null) {
getLog().warn("Project does not define minimum Maven version required for build");
} else {
logLine(false, "Project requires minimum Maven version for build of: " + specMavenVersion);
}
logLine(false, "Plugins require minimum Maven version of: " + minMavenVersion);
if (superPomDrivingMinVersion) {
logLine(false, "Note: the super-pom from Maven " + curMavenVersion + " defines some of the plugin");
logLine(false, " versions and may be influencing the plugins required minimum Maven");
logLine(false, " version.");
}
logLine(false, "");
if (isMavenPluginProject()) {
if (specMavenVersion == null) {
getLog().warn("Project (which is a Maven plugin) does not define required minimum version of Maven.");
getLog().warn("Update the pom.xml to contain");
getLog().warn(" ");
getLog().warn(" ");
getLog().warn(" ");
getLog().warn("To build this plugin you need at least Maven " + minMavenVersion);
getLog().warn("A Maven Enforcer rule can be used to enforce this if you have not already set one up");
getLog().warn("See https://maven.apache.org/enforcer/enforcer-rules/requireMavenVersion.html");
} else if (minMavenVersion != null && compare(specMavenVersion, minMavenVersion) < 0) {
getLog().warn("Project (which is a Maven plugin) targets Maven " + specMavenVersion + " or newer");
getLog().warn("but requires Maven " + minMavenVersion + " or newer to build.");
getLog().warn("This may or may not be a problem. A Maven Enforcer rule can help ");
getLog().warn("enforce that the correct version of Maven is used to build this plugin.");
getLog().warn("See https://maven.apache.org/enforcer/enforcer-rules/requireMavenVersion.html");
} else {
logLine(false, "No plugins require a newer version of Maven than specified by the pom.");
}
} else {
if (specMavenVersion == null) {
logLine(true, "Project does not define required minimum version of Maven.");
logLine(true, "Update the pom.xml to contain maven-enforcer-plugin to");
logLine(true, "force the Maven version which is needed to build this project.");
logLine(true, "See https://maven.apache.org/enforcer/enforcer-rules/requireMavenVersion.html");
logLine(true, "Using the minimum version of Maven: " + minMavenVersion);
} else if (minMavenVersion != null && compare(specMavenVersion, minMavenVersion) < 0) {
logLine(true, "Project requires an incorrect minimum version of Maven.");
logLine(true, "Update the pom.xml to contain maven-enforcer-plugin to");
logLine(true, "force the Maven version which is needed to build this project.");
logLine(true, "See https://maven.apache.org/enforcer/enforcer-rules/requireMavenVersion.html");
logLine(true, "Using the minimum version of Maven: " + specMavenVersion);
} else {
logLine(false, "No plugins require a newer version of Maven than specified by the pom.");
}
}
// updates if minimum Maven version is changed
for (Map.Entry> mavenUpgrade : mavenUpgrades.entrySet()) {
ArtifactVersion mavenUpgradeVersion = mavenUpgrade.getKey();
Map upgradePlugins = mavenUpgrade.getValue();
if (upgradePlugins.isEmpty() || compare(mavenUpgradeVersion, specMavenVersion) < 0) {
continue;
}
logLine(false, "");
logLine(false, "Require Maven " + mavenUpgradeVersion + " to use the following plugin updates:");
for (Map.Entry entry : upgradePlugins.entrySet()) {
logLine(false, entry.getValue());
}
}
logLine(false, "");
}
private Pair getEffectivePluginVersion(
Plugin plugin,
String effectiveVersion,
ArtifactVersion specMavenVersion,
ArtifactVersion curMavenVersion,
Map> mavenUpgrades)
throws MojoExecutionException, VersionRetrievalException {
Artifact artifactRange =
getHelper().createPluginArtifact(plugin.getGroupId(), plugin.getArtifactId(), effectiveVersion);
ArtifactVersion[] newerVersions =
getHelper().lookupArtifactVersions(artifactRange, true).getVersions(this.allowSnapshots);
ArtifactVersion minRequires = null;
ArtifactVersion artifactVersion = null;
for (int j = newerVersions.length - 1; j >= 0; j--) {
try {
ArtifactVersion pluginRequires = getPrerequisitesMavenVersion(
getPluginProject(plugin.getGroupId(), plugin.getArtifactId(), newerVersions[j].toString()));
if (artifactVersion == null && compare(specMavenVersion, pluginRequires) >= 0) {
// ok, newer version compatible with current specMavenVersion
artifactVersion = newerVersions[j];
}
if (effectiveVersion == null && compare(curMavenVersion, pluginRequires) >= 0) {
// version was unspecified, current version of maven thinks it should use this
effectiveVersion = newerVersions[j].toString();
}
if (artifactVersion != null && effectiveVersion != null) {
// no need to look at any older versions: latest compatible found
break;
}
// newer version not compatible with current specMavenVersion: track opportunity if Maven spec
// upgrade
if (minRequires == null || compare(minRequires, pluginRequires) > 0) {
Map upgradePlugins =
mavenUpgrades.computeIfAbsent(pluginRequires, k -> new LinkedHashMap<>());
String upgradePluginKey = compactKey(plugin.getGroupId(), plugin.getArtifactId());
if (!upgradePlugins.containsKey(upgradePluginKey)) {
String newer = newerVersions[j].toString();
if (newer.equals(effectiveVersion)) {
// plugin version configured that require a Maven version higher than spec
upgradePlugins.put(
upgradePluginKey,
pad(upgradePluginKey, INFO_PAD_SIZE + getOutputLineWidthOffset(), newer));
} else {
// plugin that can be upgraded
upgradePlugins.put(
upgradePluginKey,
pad(
upgradePluginKey,
INFO_PAD_SIZE + getOutputLineWidthOffset(),
effectiveVersion,
" -> ",
newer));
}
}
minRequires = pluginRequires;
}
} catch (ArtifactResolutionException | ProjectBuildingException e) {
// ignore bad version
}
}
return new ImmutablePair<>(artifactVersion, effectiveVersion);
}
/**
* Builds a {@link MavenProject} instance for the plugin with a given {@code groupId},
* {@code artifactId}, and {@code version}.
*
* @param groupId {@code groupId} of the plugin
* @param artifactId {@code artifactId} of the plugin
* @param version {@code version} of the plugin
* @return retrieved {@link MavenProject} instance for the given plugin
* @throws MojoExecutionException thrown if the artifact for the plugin could not be constructed
* @throws ProjectBuildingException thrown if the {@link MavenProject} instance could not be constructed
*/
private MavenProject getPluginProject(String groupId, String artifactId, String version)
throws MojoExecutionException, ProjectBuildingException, ArtifactResolutionException {
Artifact probe = getHelper()
.createDependencyArtifact(DependencyBuilder.newBuilder()
.withGroupId(groupId)
.withArtifactId(artifactId)
.withVersion(version)
.withType("pom")
.withScope(Artifact.SCOPE_RUNTIME)
.build());
getHelper().resolveArtifact(probe, true);
ProjectBuildingResult result = projectBuilder.build(
probe,
true,
PomHelper.createProjectBuilderRequest(
session,
r -> r.setProcessPlugins(false),
r -> r.setRemoteRepositories(session.getCurrentProject().getRemoteArtifactRepositories()),
r -> r.setPluginArtifactRepositories(
session.getCurrentProject().getPluginArtifactRepositories())));
if (!result.getProblems().isEmpty()) {
getLog().warn("Problems encountered during construction of the plugin POM for " + probe.toString());
result.getProblems().forEach(p -> getLog().warn("\t" + p.getMessage()));
}
return result.getProject();
}
private static String pad(String start, int len, String... ends) {
StringBuilder buf = new StringBuilder(len).append(" ").append(start).append(' ');
int padding = len
- Arrays.stream(ends)
.map(String::valueOf)
.map(String::length)
.reduce(Integer::sum)
.orElse(0);
IntStream.range(0, padding - buf.length()).forEach(ignored -> buf.append('.'));
buf.append(' ');
Arrays.stream(ends).forEach(buf::append);
return buf.toString();
}
private Map getParentsPlugins(List parents) throws MojoExecutionException {
Map parentPlugins = new HashMap<>();
for (MavenProject parentProject : parents) {
getLog().debug("Processing parent: " + parentProject.getGroupId() + ":" + parentProject.getArtifactId()
+ ":" + parentProject.getVersion() + " -> " + parentProject.getFile());
StringWriter writer = new StringWriter();
boolean havePom = false;
Model interpolatedModel;
Model originalModel = parentProject.getOriginalModel();
if (originalModel == null) {
getLog().warn("project.getOriginalModel()==null for " + parentProject.getGroupId() + ":"
+ parentProject.getArtifactId() + ":" + parentProject.getVersion()
+ " is null, substituting project.getModel()");
originalModel = parentProject.getModel();
}
try {
new MavenXpp3Writer().write(writer, originalModel);
writer.close();
havePom = true;
} catch (IOException e) {
// ignore
}
ModelBuildingRequest modelBuildingRequest = new DefaultModelBuildingRequest();
modelBuildingRequest.setUserProperties(getProject().getProperties());
interpolatedModel = modelInterpolator.interpolateModel(
originalModel, null, modelBuildingRequest, new IgnoringModelProblemCollector());
if (havePom) {
try (ByteArrayInputStream bais =
new ByteArrayInputStream(writer.toString().getBytes())) {
try (MutableXMLStreamReader pomReader = new MutableXMLStreamReader(
bais,
ofNullable(parentProject.getFile())
.map(File::toPath)
.orElse(null))) {
Set withVersionSpecified = findPluginsWithVersionsSpecified(pomReader);
Map map = getPluginManagement(interpolatedModel);
map.keySet().retainAll(withVersionSpecified);
parentPlugins.putAll(map);
map = getBuildPlugins(interpolatedModel, true);
map.keySet().retainAll(withVersionSpecified);
parentPlugins.putAll(map);
map = getReportPlugins(interpolatedModel, true);
map.keySet().retainAll(withVersionSpecified);
parentPlugins.putAll(map);
}
} catch (XMLStreamException | TransformerException | IOException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
} else {
parentPlugins.putAll(getPluginManagement(interpolatedModel));
parentPlugins.putAll(getBuildPlugins(interpolatedModel, true));
parentPlugins.putAll(getReportPlugins(interpolatedModel, true));
}
}
return parentPlugins;
}
private boolean isMavenPluginProject() {
return "maven-plugin".equals(getProject().getPackaging());
}
private String compactKey(String groupId, String artifactId) {
return PomHelper.APACHE_MAVEN_PLUGINS_GROUPID.equals(groupId)
// a core plugin... group id is not needed
? artifactId
: groupId + ":" + artifactId;
}
private static final class StackState {
private final String path;
private String groupId;
private String artifactId;
private String version;
StackState(String path) {
this.path = path;
}
public String toString() {
return path + "[groupId=" + groupId + ", artifactId=" + artifactId + ", version=" + version + "]";
}
}
/**
* Returns a set of Strings which correspond to the plugin coordinates where there is a version specified.
*
* @param pom a {@link MutableXMLStreamReader} instance for the pom project to get the plugins with versions specified.
* @return a set of Strings which correspond to the plugin coordinates where there is a version specified.
*/
private Set findPluginsWithVersionsSpecified(MutableXMLStreamReader pom)
throws XMLStreamException, IOException, TransformerException {
Set result = new HashSet<>();
Stack pathStack = new Stack<>();
StackState curState = new StackState("");
while (pom.hasNext()) {
pom.next();
if (pom.isStartElement()) {
if (curState != null
&& PATTERN_PROJECT_PLUGIN.matcher(curState.path).matches()) {
if ("groupId".equals(pom.getLocalName())) {
curState.groupId = pom.getElementText().trim();
continue;
} else if ("artifactId".equals(pom.getLocalName())) {
curState.artifactId = pom.getElementText().trim();
continue;
} else if ("version".equals(pom.getLocalName())) {
curState.version = pom.getElementText().trim();
continue;
}
}
assert curState != null;
pathStack.push(curState);
curState = new StackState(curState.path + "/" + pom.getLocalName());
}
// for empty elements, pom can be both start- and end element
if (pom.isEndElement()) {
if (curState != null
&& PATTERN_PROJECT_PLUGIN.matcher(curState.path).matches()) {
if (curState.artifactId != null && curState.version != null) {
if (curState.groupId == null) {
curState.groupId = PomHelper.APACHE_MAVEN_PLUGINS_GROUPID;
}
result.add(curState.groupId + ":" + curState.artifactId);
}
}
curState = pathStack.pop();
}
}
return result;
}
// -------------------------- OTHER METHODS --------------------------
/**
* Get the minimum required Maven version of the given plugin
* Same logic as in
*
* @param pluginProject the plugin for which to retrieve the minimum Maven version which is required
* @return The minimally required Maven version (never {@code null})
* @see PluginReport
*/
private ArtifactVersion getPrerequisitesMavenVersion(MavenProject pluginProject) {
return ofNullable(pluginProject.getPrerequisites())
.map(Prerequisites::getMaven)
.map(DefaultArtifactVersionCache::of)
.orElse(null);
}
/**
* Retrieves plugins from the given model
*
* @param build build
* @param onlyIncludeInherited {@code true} to only return the plugins definitions that will be inherited by
* child projects.
* @return map of plugin name x version
*/
private Map getPluginsFromBuild(BuildBase build, boolean onlyIncludeInherited) {
return ofNullable(build)
.flatMap(b -> ofNullable(b.getPlugins()).map(plugins -> plugins.stream()
.filter(plugin -> plugin.getVersion() != null)
.filter(plugin -> !onlyIncludeInherited || getPluginInherited(plugin))
.collect(toMap(Plugin::getKey, Plugin::getVersion))))
.orElse(emptyMap());
}
/**
* Gets the build plugins of a specific project.
*
* @param model the model to get the build plugins from.
* @param onlyIncludeInherited {@code true} to only return the plugins definitions that will be inherited by
* child projects.
* @return The map of effective plugin versions keyed by coordinates.
* @since 1.0-alpha-1
*/
private Map getBuildPlugins(Model model, boolean onlyIncludeInherited) {
Map buildPlugins = new HashMap<>(getPluginsFromBuild(model.getBuild(), onlyIncludeInherited));
ofNullable(model.getProfiles()).ifPresent(profiles -> profiles.stream()
.map(profile -> getPluginsFromBuild(profile.getBuild(), onlyIncludeInherited))
.forEach(buildPlugins::putAll));
return buildPlugins;
}
/**
* Returns the Inherited of a {@link Plugin} or {@link ReportPlugin}
*
* @param plugin the {@link Plugin} or {@link ReportPlugin}
* @return the Inherited of the {@link Plugin} or {@link ReportPlugin}
* @since 1.0-alpha-1
*/
private static boolean getPluginInherited(Object plugin) {
return "true"
.equalsIgnoreCase(
plugin instanceof ReportPlugin
? ((ReportPlugin) plugin).getInherited()
: ((Plugin) plugin).getInherited());
}
/**
* Gets the plugins that are bound to the defined phases. This does not find plugins bound in the pom to a phase
* later than the plugin is executing.
*
* @param project the project
* @return the bound plugins
*/
// pilfered this from enforcer-rules
// TODO coordinate with Brian Fox to remove the duplicate code
private Stream getBoundPlugins(MavenProject project) {
// we need to provide a copy with the version blanked out so that inferring from super-pom
// works as for 2.x as 3.x fills in the version on us!
return lifecycleExecutor.getPluginsBoundByDefaultToAllLifecycles(project.getPackaging()).stream()
.map(p -> new Plugin() {
{
setGroupId(p.getGroupId());
setArtifactId(p.getArtifactId());
}
});
}
/**
* Returns all the parent projects of the specified project, with the root project first.
*
* @param project The maven project to get the parents of
* @return the parent projects of the specified project, with the root project first.
* @throws org.apache.maven.plugin.MojoExecutionException if the super-pom could not be created.
* @since 1.0-alpha-1
*/
private List getParentProjects(MavenProject project) throws MojoExecutionException {
List parents = new ArrayList<>();
while (project.getParent() != null) {
project = project.getParent();
parents.add(0, project);
}
return parents;
}
/**
* Returns the set of all plugins used by the project.
*
* @param superPomPluginManagement the super pom's pluginManagement plugins.
* @param parentPluginManagement the parent pom's pluginManagement plugins.
* @param parentBuildPlugins the parent pom's build plugins.
* @param parentReportPlugins the parent pom's report plugins.
* @param pluginsWithVersionsSpecified the plugin coords that have a version defined in the project.
* @return the set of plugins used by the project.
*/
@SuppressWarnings("checkstyle:MethodLength")
private Set getPluginManagementPlugins(
Map superPomPluginManagement,
Map parentPluginManagement,
Map parentBuildPlugins,
Map parentReportPlugins,
Set pluginsWithVersionsSpecified) {
getLog().debug("Building list of project plugins...");
if (getLog().isDebugEnabled()) {
StringWriter origModel = new StringWriter();
try {
origModel.write("Original model:\n");
getProject().writeOriginalModel(origModel);
getLog().debug(origModel.toString());
} catch (IOException e) {
// ignore
}
}
ModelBuildingRequest modelBuildingRequest = new DefaultModelBuildingRequest();
modelBuildingRequest.setUserProperties(getProject().getProperties());
Model originalModel = modelInterpolator.interpolateModel(
getProject().getOriginalModel(),
getProject().getBasedir(),
modelBuildingRequest,
new IgnoringModelProblemCollector());
Map excludePluginManagement = new HashMap<>(superPomPluginManagement);
excludePluginManagement.putAll(parentPluginManagement);
debugVersionMap("super-pom version map", superPomPluginManagement);
debugVersionMap("parent version map", parentPluginManagement);
debugVersionMap("aggregate version map", excludePluginManagement);
excludePluginManagement.keySet().removeAll(pluginsWithVersionsSpecified);
debugVersionMap("final aggregate version map", excludePluginManagement);
Map plugins = new HashMap<>();
ofNullable(originalModel.getBuild())
.map(DisplayPluginUpdatesMojo::getPluginManagementPlugins)
.ifPresent(p -> mergePluginsMap(plugins, p, excludePluginManagement));
debugPluginMap("after adding local pluginManagement", plugins);
mergePluginsMap(plugins, getLifecyclePlugins(parentPluginManagement), parentPluginManagement);
debugPluginMap("after adding lifecycle plugins", plugins);
debugPluginMap("after adding lifecycle plugins", plugins);
ofNullable(originalModel.getBuild())
.map(b -> getBuildPlugins(b, parentPluginManagement))
.ifPresent(p -> mergePluginsMap(plugins, p, parentPluginManagement));
debugPluginMap("after adding build plugins", plugins);
ofNullable(originalModel.getReporting())
.map(r -> getReportingPlugins(r, parentPluginManagement))
.ifPresent(p -> mergePluginsMap(plugins, p, parentReportPlugins));
debugPluginMap("after adding reporting plugins", plugins);
for (Profile profile : originalModel.getProfiles()) {
if (getLog().isDebugEnabled()) {
getLog().debug("Processing profile " + profile.getId());
}
ofNullable(profile.getBuild())
.map(DisplayPluginUpdatesMojo::getPluginManagementPlugins)
.ifPresent(p -> mergePluginsMap(plugins, p, excludePluginManagement));
debugPluginMap("after adding profile " + profile.getId() + " pluginManagement", plugins);
ofNullable(profile.getBuild())
.map(b -> getBuildPlugins(b, parentPluginManagement))
.ifPresent(p -> mergePluginsMap(plugins, p, parentBuildPlugins));
debugPluginMap("after adding profile " + profile.getId() + " build plugins", plugins);
ofNullable(profile.getReporting())
.map(r -> getReportingPlugins(r, parentPluginManagement))
.ifPresent(p -> mergePluginsMap(plugins, p, parentReportPlugins));
debugPluginMap("after adding profile " + profile.getId() + " reporting plugins", plugins);
}
Set result = new TreeSet<>(PluginComparator.INSTANCE);
result.addAll(plugins.values());
return result;
}
private static Stream getReportingPlugins(Reporting reporting, Map parentPluginManagement) {
return reporting.getPlugins().stream()
// removing plugins without a version
// and with the parent also not defining it for them
.filter(plugin -> plugin.getVersion() != null || parentPluginManagement.get(plugin.getKey()) == null)
.map(DisplayPluginUpdatesMojo::toPlugin);
}
private static Stream getPluginManagementPlugins(BuildBase buildBase) {
return ofNullable(buildBase.getPluginManagement())
.map(PluginManagement::getPlugins)
.map(Collection::stream)
.orElse(Stream.empty());
}
private static Stream getBuildPlugins(BuildBase buildBase, Map parentPluginManagement) {
return buildBase.getPlugins().stream()
// removing plugins without a version
// and with the parent also not defining it for them
.filter(plugin -> plugin.getVersion() != null || parentPluginManagement.get(plugin.getKey()) == null);
}
private Stream getLifecyclePlugins(Map parentPluginManagement) {
return getBoundPlugins(getProject())
.filter(Objects::nonNull)
.filter(p -> p.getKey() != null)
.filter(p -> p.getVersion() != null)
.filter(p -> parentPluginManagement.get(p.getKey()) != null);
}
/**
* Adds those project plugins which are not inherited from the parent definitions to the list of plugins.
*
* @param plugins map of plugins to merge into
* @param pluginsToMerge plugins that need to be merged with the map
* @param parentDefinitions The parent plugin definitions.
* @since 1.0-alpha-1
*/
private void mergePluginsMap(
Map plugins, Stream pluginsToMerge, Map parentDefinitions) {
pluginsToMerge.forEach(plugin -> {
plugins.compute(plugin.getKey(), (key, existingVal) -> {
String versionFromParent = parentDefinitions.get(key);
if (plugin.getVersion() == null
&& (existingVal == null || existingVal.getVersion() == null)
&& versionFromParent != null) {
// if the plugin is not present in plugins or if it is, it doesn't have a version,
// but the parent does have it -> take the version from parent
Plugin parentPlugin = new Plugin();
parentPlugin.setGroupId(plugin.getGroupId());
parentPlugin.setArtifactId(plugin.getArtifactId());
parentPlugin.setVersion(versionFromParent);
return parentPlugin;
} else if ((versionFromParent == null || !versionFromParent.equals(plugin.getVersion()))
&& (existingVal == null || existingVal.getVersion() == null)) {
// if parent doesn't contain the plugin key or its version differs from plugin version
// and currently stored version is either null or not there
return plugin;
}
// otherwise, put the new value in the map only if existingVal is null
if (!processUnboundPlugins) {
return existingVal != null ? existingVal : plugin;
} else {
return plugin.getVersion() != null ? plugin : existingVal;
}
});
});
}
/**
* Logs at debug level a map of plugins keyed by versionless key.
*
* @param description log description
* @param plugins a map with keys being the {@link String} corresponding to the versionless artifact key and
* values
* being {@link Plugin} or {@link ReportPlugin}.
*/
private void debugPluginMap(String description, Map plugins) {
if (getLog().isDebugEnabled()) {
Set sorted = new TreeSet<>(PluginComparator.INSTANCE);
sorted.addAll(plugins.values());
getLog().debug(sorted.stream()
.collect(
() -> new StringBuilder(description),
(s, e) -> s.append("\n ")
.append(e.getKey())
.append(":")
.append(e.getVersion()),
StringBuilder::append));
}
}
/**
* Logs at debug level a map of plugin versions keyed by versionless key.
*
* @param description log description
* @param pluginVersions a map with keys being the {@link String} corresponding to the versionless artifact key and
* values
* being {@link String} plugin version.
*/
private void debugVersionMap(String description, Map pluginVersions) {
if (getLog().isDebugEnabled()) {
getLog().debug(pluginVersions.entrySet().stream()
.collect(
() -> new StringBuilder(description),
(s, e) -> s.append("\n ")
.append(e.getKey())
.append(":")
.append(e.getValue()),
StringBuilder::append));
}
}
private static Plugin toPlugin(ReportPlugin reportPlugin) {
Plugin plugin = new Plugin();
plugin.setGroupId(reportPlugin.getGroupId());
plugin.setArtifactId(reportPlugin.getArtifactId());
plugin.setVersion(reportPlugin.getVersion());
return plugin;
}
private static Stream toPlugins(Collection reportPlugins) {
return reportPlugins.stream().map(DisplayPluginUpdatesMojo::toPlugin);
}
/**
* Gets the report plugins of a specific project.
*
* @param model the model to get the report plugins from.
* @param onlyIncludeInherited true
to only return the plugins definitions that will be inherited by
* child projects.
* @return The map of effective plugin versions keyed by coordinates.
* @since 1.0-alpha-1
*/
private Map getReportPlugins(Model model, boolean onlyIncludeInherited) {
return Stream.concat(
ofNullable(model.getReporting())
.map(Reporting::getPlugins)
.map(Collection::stream)
.orElse(Stream.empty()),
ofNullable(model.getProfiles())
.flatMap(profiles -> profiles.stream()
.map(Profile::getReporting)
.filter(Objects::nonNull)
.map(Reporting::getPlugins)
.map(Collection::stream)
.reduce(Stream::concat))
.orElse(Stream.empty()))
.filter(p -> p.getVersion() != null)
.filter(p -> !onlyIncludeInherited || getPluginInherited(p))
.collect(toMap(ReportPlugin::getKey, ReportPlugin::getVersion));
}
/**
* @param pom the pom to update.
* @see AbstractVersionsUpdaterMojo#update(MutableXMLStreamReader)
* @since 1.0-alpha-1
*/
protected void update(MutableXMLStreamReader pom) {
// do nothing
}
private static int compare(ArtifactVersion a, ArtifactVersion b) {
return a == null ? b == null ? 0 : -1 : b == null ? 1 : a.compareTo(b);
}
private static class IgnoringModelProblemCollector implements ModelProblemCollector {
@Override
public void add(ModelProblemCollectorRequest req) {
// ignore
}
}
}