package org.apache.maven.plugins.dependency.analyze; /* * 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 java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.maven.artifact.Artifact; import org.apache.maven.model.Dependency; import org.apache.maven.model.DependencyManagement; import org.apache.maven.model.Exclusion; import org.apache.maven.plugin.AbstractMojo; 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.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.util.StringUtils; /** * This mojo looks at the dependencies after final resolution and looks for * mismatches in your dependencyManagement section. In versions of maven prior * to 2.0.6, it was possible to inherit versions that didn't match your * dependencyManagement. See <a * href="https://issues.apache.org/jira/browse/MNG-1577">MNG-1577</a> for more info. * This mojo is also useful for just detecting projects that override the * dependencyManagement directly. Set ignoreDirect to false to detect these * otherwise normal conditions. * * @author <a href="mailto:brianefox@gmail.com">Brian Fox</a> * @version $Id$ * @since 2.0-alpha-3 */ @Mojo( name = "analyze-dep-mgt", requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true ) public class AnalyzeDepMgt extends AbstractMojo { // fields ----------------------------------------------------------------- /** * */ @Parameter( defaultValue = "${project}", readonly = true, required = true ) private MavenProject project; /** * Fail the build if a problem is detected. */ @Parameter( property = "mdep.analyze.failBuild", defaultValue = "false" ) private boolean failBuild = false; /** * Ignore Direct Dependency Overrides of dependencyManagement section. */ @Parameter( property = "mdep.analyze.ignore.direct", defaultValue = "true" ) private boolean ignoreDirect = true; /** * Skip plugin execution completely. * * @since 2.7 */ @Parameter( property = "mdep.analyze.skip", defaultValue = "false" ) private boolean skip; // Mojo methods ----------------------------------------------------------- /* * @see org.apache.maven.plugin.Mojo#execute() */ @Override public void execute() throws MojoExecutionException, MojoFailureException { if ( skip ) { getLog().info( "Skipping plugin execution" ); return; } boolean result = checkDependencyManagement(); if ( result ) { if ( this.failBuild ) { throw new MojoExecutionException( "Found Dependency errors." ); } else { getLog().warn( "Potential problems found in Dependency Management " ); } } } /** * Does the work of checking the DependencyManagement Section. * * @return true if errors are found. * @throws MojoExecutionException */ private boolean checkDependencyManagement() throws MojoExecutionException { boolean foundError = false; getLog().info( "Found Resolved Dependency/DependencyManagement mismatches:" ); List<Dependency> depMgtDependencies = null; DependencyManagement depMgt = project.getDependencyManagement(); if ( depMgt != null ) { depMgtDependencies = depMgt.getDependencies(); } if ( depMgtDependencies != null && !depMgtDependencies.isEmpty() ) { // put all the dependencies from depMgt into a map for quick lookup Map<String, Dependency> depMgtMap = new HashMap<String, Dependency>(); Map<String, Exclusion> exclusions = new HashMap<String, Exclusion>(); for ( Dependency depMgtDependency : depMgtDependencies ) { depMgtMap.put( depMgtDependency.getManagementKey(), depMgtDependency ); // now put all the exclusions into a map for quick lookup exclusions.putAll( addExclusions( depMgtDependency.getExclusions() ) ); } // get dependencies for the project (including transitive) Set<Artifact> allDependencyArtifacts = new LinkedHashSet<Artifact>( project.getArtifacts() ); // don't warn if a dependency that is directly listed overrides // depMgt. That's ok. if ( this.ignoreDirect ) { getLog().info( "\tIgnoring Direct Dependencies." ); Set<Artifact> directDependencies = project.getDependencyArtifacts(); allDependencyArtifacts.removeAll( directDependencies ); } // log exclusion errors List<Artifact> exclusionErrors = getExclusionErrors( exclusions, allDependencyArtifacts ); for ( Artifact exclusion : exclusionErrors ) { getLog().info( StringUtils.stripEnd( getArtifactManagementKey( exclusion ), ":" ) + " was excluded in DepMgt, but version " + exclusion.getVersion() + " has been found in the dependency tree." ); foundError = true; } // find and log version mismatches Map<Artifact, Dependency> mismatch = getMismatch( depMgtMap, allDependencyArtifacts ); for ( Map.Entry<Artifact, Dependency> entry : mismatch.entrySet() ) { logMismatch( entry.getKey(), entry.getValue() ); foundError = true; } if ( !foundError ) { getLog().info( "\tNone" ); } } else { getLog().info( "\tNothing in DepMgt." ); } return foundError; } /** * Returns a map of the exclusions using the Dependency ManagementKey as the * keyset. * * @param exclusionList to be added to the map. * @return a map of the exclusions using the Dependency ManagementKey as the * keyset. */ public Map<String, Exclusion> addExclusions( List<Exclusion> exclusionList ) { Map<String, Exclusion> exclusions = new HashMap<String, Exclusion>(); if ( exclusionList != null ) { for ( Exclusion exclusion : exclusionList ) { exclusions.put( getExclusionKey( exclusion ), exclusion ); } } return exclusions; } /** * Returns a List of the artifacts that should have been excluded, but were * found in the dependency tree. * * @param exclusions a map of the DependencyManagement exclusions, with the * ManagementKey as the key and Dependency as the value. * @param allDependencyArtifacts resolved artifacts to be compared. * @return list of artifacts that should have been excluded. */ public List<Artifact> getExclusionErrors( Map<String, Exclusion> exclusions, Set<Artifact> allDependencyArtifacts ) { List<Artifact> list = new ArrayList<Artifact>(); for ( Artifact artifact : allDependencyArtifacts ) { if ( exclusions.containsKey( getExclusionKey( artifact ) ) ) { list.add( artifact ); } } return list; } public String getExclusionKey( Artifact artifact ) { return artifact.getGroupId() + ":" + artifact.getArtifactId(); } public String getExclusionKey( Exclusion ex ) { return ex.getGroupId() + ":" + ex.getArtifactId(); } /** * Calculate the mismatches between the DependencyManagement and resolved * artifacts * * @param depMgtMap contains the Dependency.GetManagementKey as the keyset for * quick lookup. * @param allDependencyArtifacts contains the set of all artifacts to compare. * @return a map containing the resolved artifact as the key and the listed * dependency as the value. */ public Map<Artifact, Dependency> getMismatch( Map<String, Dependency> depMgtMap, Set<Artifact> allDependencyArtifacts ) { Map<Artifact, Dependency> mismatchMap = new HashMap<Artifact, Dependency>(); for ( Artifact dependencyArtifact : allDependencyArtifacts ) { Dependency depFromDepMgt = depMgtMap.get( getArtifactManagementKey( dependencyArtifact ) ); if ( depFromDepMgt != null ) { //workaround for MNG-2961 dependencyArtifact.isSnapshot(); if ( depFromDepMgt.getVersion() != null && !depFromDepMgt.getVersion().equals( dependencyArtifact.getBaseVersion() ) ) { mismatchMap.put( dependencyArtifact, depFromDepMgt ); } } } return mismatchMap; } /** * This function displays the log to the screen showing the versions and * information about the artifacts that don't match. * * @param dependencyArtifact the artifact that was resolved. * @param dependencyFromDepMgt the dependency listed in the DependencyManagement section. * @throws MojoExecutionException */ public void logMismatch( Artifact dependencyArtifact, Dependency dependencyFromDepMgt ) throws MojoExecutionException { if ( dependencyArtifact == null || dependencyFromDepMgt == null ) { throw new MojoExecutionException( "Invalid params: Artifact: " + dependencyArtifact + " Dependency: " + dependencyFromDepMgt ); } getLog().info( "\tDependency: " + StringUtils.stripEnd( dependencyFromDepMgt.getManagementKey(), ":" ) ); getLog().info( "\t\tDepMgt : " + dependencyFromDepMgt.getVersion() ); getLog().info( "\t\tResolved: " + dependencyArtifact.getBaseVersion() ); } /** * This function returns a string comparable with Dependency.GetManagementKey. * * @param artifact to gen the key for * @return a string in the form: groupId:ArtifactId:Type[:Classifier] */ public String getArtifactManagementKey( Artifact artifact ) { return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getType() + ( ( artifact.getClassifier() != null ) ? ":" + artifact.getClassifier() : "" ); } /** * @return the failBuild */ protected final boolean isFailBuild() { return this.failBuild; } /** * @param theFailBuild the failBuild to set */ public void setFailBuild( boolean theFailBuild ) { this.failBuild = theFailBuild; } /** * @return the project */ protected final MavenProject getProject() { return this.project; } /** * @param theProject the project to set */ public void setProject( MavenProject theProject ) { this.project = theProject; } /** * @return the ignoreDirect */ protected final boolean isIgnoreDirect() { return this.ignoreDirect; } /** * @param theIgnoreDirect the ignoreDirect to set */ public void setIgnoreDirect( boolean theIgnoreDirect ) { this.ignoreDirect = theIgnoreDirect; } }