
org.codehaus.mojo.versions.DisplayExtensionUpdatesMojo Maven / Gradle / Ivy
Show all versions of versions-maven-plugin Show documentation
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 javax.xml.stream.XMLStreamException;
import javax.xml.transform.TransformerException;
import java.io.IOException;
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.commons.lang3.StringUtils;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
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.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.MutableXMLStreamReader;
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.eclipse.aether.RepositorySystem;
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.
*
* 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;
/**
* If set to {@code false}, the plugin will not interpolate property values when looking for versions
* to be changed, but will instead operate on raw model.
*
* @since 2.15.0
*/
@Parameter(property = "interpolateProperties", defaultValue = "true")
protected boolean interpolateProperties = 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(
ArtifactHandlerManager artifactHandlerManager,
RepositorySystem repositorySystem,
Map wagonMap,
Map changeRecorders) {
super(artifactHandlerManager, repositorySystem, 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(project);
} else {
extensions = Stream.empty();
}
if (processBuildExtensions) {
if (!interpolateProperties) {
extensions = Stream.concat(
extensions,
PomHelper.getChildModels(session.getCurrentProject(), getLog()).values().stream()
.map(Model::getBuild)
.filter(Objects::nonNull)
.map(Build::getExtensions)
.map(List::stream)
.reduce(Stream::concat)
.orElse(Stream.empty()));
} else {
List rawModels = getRawModels();
for (ModelNode node : rawModels) {
if (node.getModel() == null) {
// unlikely
continue;
}
Map properties = new HashMap<>();
for (ModelNode p = node; p != null; p = p.getParent().orElse(null)) {
p.getModel()
.getProperties()
.forEach((key, value) ->
properties.putIfAbsent(String.valueOf(key), String.valueOf(value)));
}
extensions = Stream.concat(
extensions,
Optional.ofNullable(node.getModel().getBuild())
.map(Build::getExtensions)
.orElse(Collections.emptyList())
.stream()
.map(e -> ExtensionBuilder.newBuilder()
.withGroupId(PomHelper.evaluate(e.getGroupId(), properties, getLog()))
.withArtifactId(
PomHelper.evaluate(e.getArtifactId(), properties, getLog()))
.withVersion(PomHelper.evaluate(e.getVersion(), properties, getLog()))
.build()));
}
}
}
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 | XMLStreamException e) {
throw new MojoExecutionException(e.getMessage());
}
if (dependencies.isEmpty()) {
getLog().info("Extensions set filtered by include- and exclude-filters is empty. Nothing to do.");
return;
}
try {
logUpdates(getHelper().lookupDependenciesUpdates(dependencies.stream(), true, true, allowSnapshots));
} catch (VersionRetrievalException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
}
private List getRawModels() throws MojoFailureException {
List rawModels;
try {
MutableXMLStreamReader pomReader =
new MutableXMLStreamReader(getProject().getFile().toPath());
ModelNode rootNode = new ModelNode(
PomHelper.getRawModel(pomReader.getSource(), getProject().getFile()), pomReader);
rawModels = PomHelper.getRawModelTree(rootNode, getLog());
} catch (IOException | XMLStreamException | TransformerException e) {
throw new MojoFailureException(e.getMessage(), e);
}
return rawModels;
}
private Optional calculateUpdateScope() {
return of(SegmentUtils.determineUnchangedSegment(
allowMajorUpdates, allowMinorUpdates, allowIncrementalUpdates, getLog())
.map(Segment::minorTo)
.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.getCurrentVersion() != null) {
current = versions.getCurrentVersion().toString();
latest = versions.getNewestUpdateWithinSegment(calculateUpdateScope(), allowSnapshots);
} else {
ArtifactVersion newestVersion =
versions.getNewestVersion(versions.getArtifact().getVersionRange(), allowSnapshots);
current = versions.getArtifact().getVersionRange().toString();
latest = newestVersion == null
? null
: versions.getNewestUpdateWithinSegment(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(MutableXMLStreamReader)
* @since 1.0-alpha-1
*/
@Override
protected void update(MutableXMLStreamReader pom) {
// do nothing
}
}