001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.maven.enforcer.rules.dependency; 020 021import javax.inject.Inject; 022import javax.inject.Named; 023 024import java.util.ArrayList; 025import java.util.Arrays; 026import java.util.Collections; 027import java.util.List; 028import java.util.Objects; 029 030import org.apache.maven.enforcer.rule.api.EnforcerRuleException; 031import org.apache.maven.enforcer.rules.AbstractStandardEnforcerRule; 032import org.apache.maven.enforcer.rules.utils.ArtifactUtils; 033import org.eclipse.aether.graph.DependencyNode; 034 035import static org.apache.maven.artifact.Artifact.SCOPE_PROVIDED; 036import static org.apache.maven.artifact.Artifact.SCOPE_TEST; 037 038/** 039 * @author <a href="mailto:rex@e-hoffman.org">Rex Hoffman</a> 040 */ 041@Named("dependencyConvergence") 042public final class DependencyConvergence extends AbstractStandardEnforcerRule { 043 044 // parameters 045 046 private boolean uniqueVersions; 047 048 private List<String> includes; 049 050 private List<String> excludes; 051 052 private List<String> excludedScopes = Arrays.asList(SCOPE_TEST, SCOPE_PROVIDED); 053 054 // parameters - end 055 056 private DependencyVersionMap dependencyVersionMap; 057 058 private final ResolverUtil resolverUtil; 059 060 @Inject 061 public DependencyConvergence(ResolverUtil resolverUtil) { 062 this.resolverUtil = Objects.requireNonNull(resolverUtil); 063 } 064 065 @Override 066 public void execute() throws EnforcerRuleException { 067 068 DependencyNode node = resolverUtil.resolveTransitiveDependenciesVerbose(excludedScopes); 069 dependencyVersionMap = new DependencyVersionMap().setUniqueVersions(uniqueVersions); 070 node.accept(dependencyVersionMap); 071 072 List<String> errorMsgs = 073 getConvergenceErrorMsgs(dependencyVersionMap.getConflictedVersionNumbers(includes, excludes)); 074 075 if (!errorMsgs.isEmpty()) { 076 throw new EnforcerRuleException("Failed while enforcing releasability." + System.lineSeparator() 077 + String.join(System.lineSeparator(), errorMsgs)); 078 } 079 } 080 081 private StringBuilder buildTreeString(DependencyNode node) { 082 List<String> loc = new ArrayList<>(); 083 DependencyNode currentNode = node; 084 while (currentNode != null) { 085 // ArtifactUtils.toArtifact(node) adds scope and optional information, if present 086 loc.add(ArtifactUtils.toArtifact(currentNode).toString()); 087 currentNode = dependencyVersionMap.getParent(currentNode); 088 } 089 Collections.reverse(loc); 090 StringBuilder builder = new StringBuilder(); 091 for (int i = 0; i < loc.size(); i++) { 092 for (int j = 0; j < i; j++) { 093 builder.append(" "); 094 } 095 builder.append("+-").append(loc.get(i)).append(System.lineSeparator()); 096 } 097 return builder; 098 } 099 100 private List<String> getConvergenceErrorMsgs(List<List<DependencyNode>> errors) { 101 List<String> errorMsgs = new ArrayList<>(); 102 for (List<DependencyNode> nodeList : errors) { 103 errorMsgs.add(buildConvergenceErrorMsg(nodeList)); 104 } 105 return errorMsgs; 106 } 107 108 private String buildConvergenceErrorMsg(List<DependencyNode> nodeList) { 109 StringBuilder builder = new StringBuilder(); 110 builder.append(System.lineSeparator()) 111 .append("Dependency convergence error for ") 112 .append(nodeList.get(0).getArtifact().toString()) 113 .append(" paths to dependency are:") 114 .append(System.lineSeparator()); 115 if (nodeList.size() > 0) { 116 builder.append(buildTreeString(nodeList.get(0))); 117 } 118 for (DependencyNode node : nodeList.subList(1, nodeList.size())) { 119 builder.append("and").append(System.lineSeparator()).append(buildTreeString(node)); 120 } 121 return builder.toString(); 122 } 123 124 @Override 125 public String toString() { 126 return String.format( 127 "DependencyConvergence[includes=%s, excludes=%s, uniqueVersions=%b]", 128 includes, excludes, uniqueVersions); 129 } 130}