All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.mojo.versions.CompareDependenciesMojo Maven / Gradle / Ivy

Go to download

Versions Plugin for Maven. The Versions Plugin updates the versions of components in the POM.

The newest version!
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 java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.model.Dependency;
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.wagon.Wagon;
import org.codehaus.mojo.versions.api.ArtifactAssociation;
import org.codehaus.mojo.versions.api.PomHelper;
import org.codehaus.mojo.versions.api.Property;
import org.codehaus.mojo.versions.api.PropertyVersions;
import org.codehaus.mojo.versions.api.VersionsHelper;
import org.codehaus.mojo.versions.api.recording.ChangeRecorder;
import org.codehaus.mojo.versions.api.recording.DependencyChangeRecord;
import org.codehaus.mojo.versions.rewriting.MutableXMLStreamReader;
import org.codehaus.mojo.versions.utils.DependencyBuilder;
import org.eclipse.aether.RepositorySystem;

import static java.util.Collections.singletonList;

/**
 * Compare dependency versions of the current project to dependencies or dependency management of a remote repository
 * project. Can optionally update locally the project instead of reporting the comparison
 *
 * @author Paul Gier
 * @since 1.3
 */
@Mojo(name = "compare-dependencies", threadSafe = true)
public class CompareDependenciesMojo extends AbstractVersionsDependencyUpdaterMojo {

    /**
     * The width to pad info messages.
     *
     * @since 1.0-alpha-1
     */
    private static final int INFO_PAD_SIZE = 68;

    /**
     * The groupId, artifactId, and version of the remote project (POM) to which we are comparing. This should be in the
     * form "groupId:artifactId:version"
     */
    @Parameter(property = "remotePom", required = true)
    protected String remotePom;

    /**
     * Ignore the list of remote dependencies and only compare the remote dependencyManagement
     */
    @Parameter(property = "ignoreRemoteDependencies", defaultValue = "false")
    protected boolean ignoreRemoteDependencies;

    /**
     * Ignore the remote dependency management and only check against the actual dependencies of the remote project
     */
    @Parameter(property = "ignoreRemoteDependencyManagement", defaultValue = "false")
    protected boolean ignoreRemoteDependencyManagement;

    /**
     * Update dependency versions in the current POM.
     */
    @Parameter(property = "updateDependencies", defaultValue = "false")
    protected boolean updateDependencies;

    /**
     * Update dependency versions stored in properties
     */
    @Parameter(property = "updatePropertyVersions", defaultValue = "false")
    protected boolean updatePropertyVersions;

    /**
     * Display the dependency version differences on the command line, but do not update the versions in the current
     * pom. If updateDependencies is set to "true" this will automatically be set to false.
     */
    @Parameter(property = "reportMode", defaultValue = "true")
    protected boolean reportMode;

    /**
     * If the output file is set, the diff report will be written to this file.
     */
    @Parameter(property = "reportOutputFile")
    protected File reportOutputFile;

    /**
     * The (injected) instance of {@link ProjectBuilder}
     *
     * @since 2.14.0
     */
    protected final ProjectBuilder projectBuilder;

    // ------------------------------ METHODS --------------------------

    @Inject
    public CompareDependenciesMojo(
            ArtifactHandlerManager artifactHandlerManager,
            RepositorySystem repositorySystem,
            Map wagonMap,
            ProjectBuilder projectBuilder,
            Map changeRecorders) {
        super(artifactHandlerManager, repositorySystem, wagonMap, changeRecorders);
        this.projectBuilder = projectBuilder;
    }

    /**
     * @param pom the pom to update.
     * @throws org.apache.maven.plugin.MojoExecutionException Something wrong with the plugin itself
     * @throws org.apache.maven.plugin.MojoFailureException   The plugin detected an error in the build
     * @throws javax.xml.stream.XMLStreamException            when things go wrong with XML streaming
     * @see AbstractVersionsUpdaterMojo#update(MutableXMLStreamReader)
     */
    protected void update(MutableXMLStreamReader pom)
            throws MojoExecutionException, MojoFailureException, XMLStreamException {
        if (this.ignoreRemoteDependencies && this.ignoreRemoteDependencyManagement) {
            throw new MojoFailureException(" ignoreRemoteDependencies and ignoreRemoteDependencyManagement "
                    + "are both set to true.  At least one of these needs to be false ");
        }

        if (updateDependencies) {
            reportMode = false;
        }

        String[] remoteGAV = this.remotePom.split(":");
        if (remoteGAV.length != 3) {
            throw new MojoFailureException(" Invalid format for remotePom: " + remotePom);
        }

        MavenProject remoteMavenProject = null;
        try {
            remoteMavenProject = getRemoteMavenProject(remoteGAV[0], remoteGAV[1], remoteGAV[2]);
        } catch (ArtifactResolutionException | ProjectBuildingException e) {
            throw new MojoFailureException(e.getMessage());
        }

        Map remoteDepsMap = new HashMap<>();
        if (!ignoreRemoteDependencyManagement) {
            List remoteProjectDepMgmtDeps = remoteMavenProject.getDependencyManagement() == null
                    ? null
                    : remoteMavenProject.getDependencyManagement().getDependencies();
            if (remoteProjectDepMgmtDeps != null) {
                remoteProjectDepMgmtDeps.forEach(dep -> remoteDepsMap.putIfAbsent(dep.getManagementKey(), dep));
            }
        }
        if (!ignoreRemoteDependencies && remoteMavenProject.getDependencies() != null) {
            remoteMavenProject.getDependencies().forEach(dep -> remoteDepsMap.putIfAbsent(dep.getManagementKey(), dep));
        }

        List totalDiffs = new ArrayList<>();
        List propertyDiffs = new ArrayList<>();
        if (getProject().getDependencyManagement() != null && isProcessingDependencyManagement()) {
            totalDiffs.addAll(compareVersions(
                    pom,
                    getProject().getDependencyManagement().getDependencies(),
                    remoteDepsMap,
                    DependencyChangeRecord.ChangeKind.DEPENDENCY_MANAGEMENT));
        }
        if (getProject().getDependencies() != null && isProcessingDependencies()) {
            totalDiffs.addAll(compareVersions(
                    pom, getProject().getDependencies(), remoteDepsMap, DependencyChangeRecord.ChangeKind.DEPENDENCY));
        }
        if (updatePropertyVersions) {
            Map versionProperties = this.getHelper()
                    .getVersionPropertiesMap(VersionsHelper.VersionPropertiesMapRequest.builder()
                            .withMavenProject(getProject())
                            .build());
            propertyDiffs.addAll(updatePropertyVersions(pom, versionProperties, remoteDepsMap));
        }
        if (getProject().getParent() != null && remoteMavenProject.getParent() != null && isProcessingParent()) {
            Dependency parent = DependencyBuilder.newBuilder()
                    .withGroupId(remoteMavenProject.getParentArtifact().getGroupId())
                    .withArtifactId(remoteMavenProject.getParentArtifact().getArtifactId())
                    .withVersion(remoteMavenProject.getParentArtifact().getVersion())
                    .withType(remoteMavenProject.getParentArtifact().getType())
                    .withScope(remoteMavenProject.getParentArtifact().getScope())
                    .withClassifier(remoteMavenProject.getParentArtifact().getClassifier())
                    .build();
            if (getLog().isDebugEnabled()) {
                getLog().debug("Processing parent dependency: " + parent);
            }
            remoteDepsMap.putIfAbsent(parent.getManagementKey(), parent);
            totalDiffs.addAll(compareVersions(
                    pom,
                    singletonList(getParentDependency()),
                    remoteDepsMap,
                    DependencyChangeRecord.ChangeKind.PARENT));
        }

        if (reportMode) {
            getLog().info("The following differences were found:");
            if (totalDiffs.isEmpty()) {
                getLog().info("  none");
            } else {
                for (String totalDiff : totalDiffs) {
                    getLog().info("  " + totalDiff);
                }
            }
            getLog().info("The following property differences were found:");
            if (propertyDiffs.isEmpty()) {
                getLog().info("  none");
            } else {
                for (String propertyDiff : propertyDiffs) {
                    getLog().info("  " + propertyDiff);
                }
            }
        }

        if (reportOutputFile != null) {
            writeReportFile(totalDiffs, propertyDiffs);
        }
    }

    /**
     * Builds a {@link MavenProject} instance for the dependency with a given {@code groupId},
     * {@code artifactId}, and {@code version}.
     * @param groupId {@code groupId} of the dependency
     * @param artifactId {@code artifactId} of the dependency
     * @param version {@code version} of the dependency
     * @return retrieved {@link MavenProject} instance for the given dependency
     * @throws MojoExecutionException thrown if the artifact for the dependency could not be constructed
     * @throws ProjectBuildingException thrown if the {@link MavenProject} instance could not be constructed
     */
    private MavenProject getRemoteMavenProject(String groupId, String artifactId, String version)
            throws MojoExecutionException, ArtifactResolutionException, ProjectBuildingException {
        Artifact remoteArtifact = toArtifact(DependencyBuilder.newBuilder()
                .withGroupId(groupId)
                .withArtifactId(artifactId)
                .withVersion(version)
                .build());
        ProjectBuildingResult result = projectBuilder.build(
                remoteArtifact,
                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 POM for " + remoteArtifact.toString());
            result.getProblems().forEach(p -> getLog().warn("\t" + p.getMessage()));
        }
        return result.getProject();
    }

    /**
     * Compare the dependency versions of the current project with the dependency versions of a remote project
     *
     * @throws XMLStreamException
     * @throws MojoExecutionException
     */
    private List compareVersions(
            MutableXMLStreamReader pom,
            List dependencies,
            Map remoteDependencies,
            DependencyChangeRecord.ChangeKind changeKind)
            throws MojoExecutionException, XMLStreamException {
        List updates = new ArrayList<>();
        for (Dependency dep : dependencies) {
            Artifact artifact = this.toArtifact(dep);
            if (!isIncluded(artifact)) {
                continue;
            }

            Dependency remoteDep = remoteDependencies.get(dep.getManagementKey());
            if (remoteDep != null) {
                String remoteVersion = remoteDep.getVersion();
                if (!dep.getVersion().equals(remoteVersion)) {
                    StringBuilder buf = writeDependencyDiffMessage(dep, remoteVersion);
                    updates.add(buf.toString());
                    if (!reportMode) {
                        updateDependencyVersion(pom, dep, remoteVersion, changeKind);
                    }
                }
            }
        }

        return updates;
    }

    /**
     * Updates the properties holding a version if necessary.
     */
    private List updatePropertyVersions(
            MutableXMLStreamReader pom,
            Map versionProperties,
            Map remoteDependencies)
            throws XMLStreamException {
        List result = new ArrayList<>();
        for (Map.Entry entry : versionProperties.entrySet()) {
            Property property = entry.getKey();
            PropertyVersions version = entry.getValue();

            String candidateVersion = computeCandidateVersion(remoteDependencies, property, version);
            if (candidateVersion != null) {
                String originalVersion =
                        version.getAssociations()[0].getArtifact().getVersion(); // Yekes
                if (!candidateVersion.equals(originalVersion)) // Update needed
                {
                    result.add(writeDiffMessage(property.getName(), originalVersion, candidateVersion)
                            .toString());
                    if (!reportMode && PomHelper.setPropertyVersion(pom, null, property.getName(), candidateVersion)) {
                        getLog().info("Updated ${" + property.getName() + "} from " + originalVersion + " to "
                                + candidateVersion);
                    }
                }
            }
        }
        return result;
    }

    /**
     * Returns the candidate version to use for the specified property.
     * 

* The dependencies currently linked to the property must all be defined by the remote POM and they should refer to * the same version. If that's the case, that same version is returned. Otherwise, null is returned * indicating that there is no candidate. * * @param remoteDependencies the remote dependencies * @param property the property to update * @param propertyVersions the association * @return the candidate version or null if there isn't any */ private String computeCandidateVersion( Map remoteDependencies, Property property, PropertyVersions propertyVersions) { String candidateVersion = null; for (ArtifactAssociation artifactAssociation : propertyVersions.getAssociations()) { String id = generateId(artifactAssociation.getArtifact()); Dependency dependency = remoteDependencies.get(id); if (dependency == null) { getLog().info("Not updating ${" + property.getName() + "}: no info for " + id); return null; } else { if (candidateVersion == null) { candidateVersion = dependency.getVersion(); } else if (!candidateVersion.equals(dependency.getVersion())) { getLog().warn("Could not update ${" + property.getName() + "}: version mismatch"); return null; } } } return candidateVersion; } private void writeReportFile(List dependenciesUpdate, List propertiesUpdate) throws MojoExecutionException { if (!reportOutputFile.getParentFile().exists()) { reportOutputFile.getParentFile().mkdirs(); } try (FileWriter fw = new FileWriter(reportOutputFile); // PrintWriter pw = new PrintWriter(fw)) { pw.println("The following differences were found:"); pw.println(); if (dependenciesUpdate.isEmpty()) { pw.println(" none"); } else { for (String dependencyUpdate : dependenciesUpdate) { pw.println(" " + dependencyUpdate); } } pw.println(); pw.println("The following property differences were found:"); pw.println(); if (propertiesUpdate.isEmpty()) { pw.println(" none"); } else { for (String propertyUpdate : propertiesUpdate) { pw.println(" " + propertyUpdate); } } } catch (IOException e) { throw new MojoExecutionException("Unable to write report file. ", e); } } /** * Create a simple message describing the version diff * * @param dep * @param remoteVersion * @return The message */ private StringBuilder writeDependencyDiffMessage(Dependency dep, String remoteVersion) { String id = dep.getGroupId() + ":" + dep.getArtifactId(); return writeDiffMessage(id, dep.getVersion(), remoteVersion); } private StringBuilder writeDiffMessage(String id, String originalVersion, String targetVersion) { StringBuilder buf = new StringBuilder(); buf.append(id); buf.append(' '); int padding = INFO_PAD_SIZE - originalVersion.length() - targetVersion.length() - 4; while (buf.length() < padding) { buf.append('.'); } buf.append(' '); buf.append(originalVersion); buf.append(" -> "); buf.append(targetVersion); return buf; } /** * Creates a key that is similar to what {@link Dependency#getManagementKey()} generates for a dependency. */ private static String generateId(Artifact artifact) { StringBuilder sb = new StringBuilder(); sb.append(artifact.getGroupId()) .append(":") .append(artifact.getArtifactId()) .append(":") .append(artifact.getType()); if (artifact.getClassifier() != null) { sb.append(":").append(artifact.getClassifier()); } return sb.toString(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy