View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.enforcer.rules.dependency;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.Collections;
27  import java.util.List;
28  import java.util.Objects;
29  
30  import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
31  import org.apache.maven.enforcer.rules.AbstractStandardEnforcerRule;
32  import org.apache.maven.enforcer.rules.utils.ArtifactUtils;
33  import org.eclipse.aether.graph.DependencyNode;
34  
35  import static org.apache.maven.artifact.Artifact.SCOPE_PROVIDED;
36  import static org.apache.maven.artifact.Artifact.SCOPE_TEST;
37  
38  /**
39   * @author <a href="mailto:rex@e-hoffman.org">Rex Hoffman</a>
40   */
41  @Named("dependencyConvergence")
42  public final class DependencyConvergence extends AbstractStandardEnforcerRule {
43  
44      // parameters
45  
46      private boolean uniqueVersions;
47  
48      private List<String> includes;
49  
50      private List<String> excludes;
51  
52      private List<String> excludedScopes = Arrays.asList(SCOPE_TEST, SCOPE_PROVIDED);
53  
54      // parameters - end
55  
56      private DependencyVersionMap dependencyVersionMap;
57  
58      private final ResolverUtil resolverUtil;
59  
60      @Inject
61      public DependencyConvergence(ResolverUtil resolverUtil) {
62          this.resolverUtil = Objects.requireNonNull(resolverUtil);
63      }
64  
65      @Override
66      public void execute() throws EnforcerRuleException {
67  
68          DependencyNode node = resolverUtil.resolveTransitiveDependenciesVerbose(excludedScopes);
69          dependencyVersionMap = new DependencyVersionMap().setUniqueVersions(uniqueVersions);
70          node.accept(dependencyVersionMap);
71  
72          List<String> errorMsgs =
73                  getConvergenceErrorMsgs(dependencyVersionMap.getConflictedVersionNumbers(includes, excludes));
74  
75          if (!errorMsgs.isEmpty()) {
76              throw new EnforcerRuleException("Failed while enforcing releasability." + System.lineSeparator()
77                      + String.join(System.lineSeparator(), errorMsgs));
78          }
79      }
80  
81      private StringBuilder buildTreeString(DependencyNode node) {
82          List<String> loc = new ArrayList<>();
83          DependencyNode currentNode = node;
84          while (currentNode != null) {
85              // ArtifactUtils.toArtifact(node) adds scope and optional information, if present
86              loc.add(ArtifactUtils.toArtifact(currentNode).toString());
87              currentNode = dependencyVersionMap.getParent(currentNode);
88          }
89          Collections.reverse(loc);
90          StringBuilder builder = new StringBuilder();
91          for (int i = 0; i < loc.size(); i++) {
92              for (int j = 0; j < i; j++) {
93                  builder.append("  ");
94              }
95              builder.append("+-").append(loc.get(i)).append(System.lineSeparator());
96          }
97          return builder;
98      }
99  
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 }