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.project;
20  
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  
29  import org.apache.maven.artifact.ArtifactUtils;
30  import org.apache.maven.model.Dependency;
31  import org.apache.maven.model.Extension;
32  import org.apache.maven.model.Parent;
33  import org.apache.maven.model.Plugin;
34  import org.codehaus.plexus.util.StringUtils;
35  import org.codehaus.plexus.util.dag.CycleDetectedException;
36  import org.codehaus.plexus.util.dag.DAG;
37  import org.codehaus.plexus.util.dag.TopologicalSorter;
38  import org.codehaus.plexus.util.dag.Vertex;
39  
40  /**
41   * ProjectSorter
42   */
43  public class ProjectSorter {
44      private DAG dag;
45  
46      private List<MavenProject> sortedProjects;
47  
48      private Map<String, MavenProject> projectMap;
49  
50      private MavenProject topLevelProject;
51  
52      /**
53       * Sort a list of projects.
54       * <ul>
55       * <li>collect all the vertices for the projects that we want to build.</li>
56       * <li>iterate through the deps of each project and if that dep is within
57       * the set of projects we want to build then add an edge, otherwise throw
58       * the edge away because that dependency is not within the set of projects
59       * we are trying to build. we assume a closed set.</li>
60       * <li>do a topo sort on the graph that remains.</li>
61       * </ul>
62       * @throws DuplicateProjectException if any projects are duplicated by id
63       */
64      // MAVENAPI FIXME: the DAG used is NOT only used to represent the dependency relation,
65      // but also for <parent>, <build><plugin>, <reports>. We need multiple DAG's
66      // since a DAG can only handle 1 type of relationship properly.
67      // Usecase:  This is detected as a cycle:
68      // org.apache.maven:maven-plugin-api                -(PARENT)->
69      // org.apache.maven:maven                           -(inherited REPORTING)->
70      // org.apache.maven.plugins:maven-checkstyle-plugin -(DEPENDENCY)->
71      // org.apache.maven:maven-plugin-api
72      // In this case, both the verify and the report goals are called
73      // in a different lifecycle. Though the compiler-plugin has a valid usecase, although
74      // that seems to work fine. We need to take versions and lifecycle into account.
75      public ProjectSorter(Collection<MavenProject> projects) throws CycleDetectedException, DuplicateProjectException {
76          dag = new DAG();
77  
78          // groupId:artifactId:version -> project
79          projectMap = new HashMap<>(projects.size() * 2);
80  
81          // groupId:artifactId -> (version -> vertex)
82          Map<String, Map<String, Vertex>> vertexMap = new HashMap<>(projects.size() * 2);
83  
84          for (MavenProject project : projects) {
85              String projectId = getId(project);
86  
87              MavenProject conflictingProject = projectMap.put(projectId, project);
88  
89              if (conflictingProject != null) {
90                  throw new DuplicateProjectException(
91                          projectId,
92                          conflictingProject.getFile(),
93                          project.getFile(),
94                          "Project '" + projectId + "' is duplicated in the reactor");
95              }
96  
97              String projectKey = ArtifactUtils.versionlessKey(project.getGroupId(), project.getArtifactId());
98  
99              Map<String, Vertex> vertices = vertexMap.get(projectKey);
100             if (vertices == null) {
101                 vertices = new HashMap<>(2, 1);
102                 vertexMap.put(projectKey, vertices);
103             }
104             vertices.put(project.getVersion(), dag.addVertex(projectId));
105         }
106 
107         for (Vertex projectVertex : dag.getVertices()) {
108             String projectId = projectVertex.getLabel();
109 
110             MavenProject project = projectMap.get(projectId);
111 
112             for (Dependency dependency : project.getDependencies()) {
113                 addEdge(
114                         projectMap,
115                         vertexMap,
116                         project,
117                         projectVertex,
118                         dependency.getGroupId(),
119                         dependency.getArtifactId(),
120                         dependency.getVersion(),
121                         false,
122                         false);
123             }
124 
125             org.apache.maven.model.Model originalModel = project.getOriginalModel();
126             if (originalModel != null && originalModel.getDependencyManagement() != null) {
127                 for (org.apache.maven.model.Dependency dependency :
128                         originalModel.getDependencyManagement().getDependencies()) {
129                     if ("import".equals(dependency.getScope()) && "pom".equals(dependency.getType())) {
130                         addEdge(
131                                 projectMap,
132                                 vertexMap,
133                                 project,
134                                 projectVertex,
135                                 dependency.getGroupId(),
136                                 dependency.getArtifactId(),
137                                 resolveImportVersion(project, dependency.getVersion()),
138                                 false,
139                                 false);
140                     }
141                 }
142             }
143 
144             Parent parent = project.getModel().getParent();
145 
146             if (parent != null) {
147                 // Parent is added as an edge, but must not cause a cycle - so we remove any other edges it has
148                 // in conflict
149                 addEdge(
150                         projectMap,
151                         vertexMap,
152                         null,
153                         projectVertex,
154                         parent.getGroupId(),
155                         parent.getArtifactId(),
156                         parent.getVersion(),
157                         true,
158                         false);
159             }
160 
161             List<Plugin> buildPlugins = project.getBuildPlugins();
162             if (buildPlugins != null) {
163                 for (Plugin plugin : buildPlugins) {
164                     addEdge(
165                             projectMap,
166                             vertexMap,
167                             project,
168                             projectVertex,
169                             plugin.getGroupId(),
170                             plugin.getArtifactId(),
171                             plugin.getVersion(),
172                             false,
173                             true);
174 
175                     for (Dependency dependency : plugin.getDependencies()) {
176                         addEdge(
177                                 projectMap,
178                                 vertexMap,
179                                 project,
180                                 projectVertex,
181                                 dependency.getGroupId(),
182                                 dependency.getArtifactId(),
183                                 dependency.getVersion(),
184                                 false,
185                                 true);
186                     }
187                 }
188             }
189 
190             List<Extension> buildExtensions = project.getBuildExtensions();
191             if (buildExtensions != null) {
192                 for (Extension extension : buildExtensions) {
193                     addEdge(
194                             projectMap,
195                             vertexMap,
196                             project,
197                             projectVertex,
198                             extension.getGroupId(),
199                             extension.getArtifactId(),
200                             extension.getVersion(),
201                             false,
202                             true);
203                 }
204             }
205         }
206 
207         List<MavenProject> sortedProjects = new ArrayList<>(projects.size());
208 
209         List<String> sortedProjectLabels = TopologicalSorter.sort(dag);
210 
211         for (String id : sortedProjectLabels) {
212             sortedProjects.add(projectMap.get(id));
213         }
214 
215         this.sortedProjects = Collections.unmodifiableList(sortedProjects);
216     }
217 
218     private static String resolveImportVersion(MavenProject project, String version) {
219         if ("${project.version}".equals(version) || "${pom.version}".equals(version)) {
220             return project.getVersion();
221         }
222         if (version != null && version.startsWith("${") && version.endsWith("}")) {
223             String property = project.getProperties().getProperty(version.substring(2, version.length() - 1));
224             if (property != null) {
225                 return property;
226             }
227         }
228         return version;
229     }
230 
231     @SuppressWarnings("checkstyle:parameternumber")
232     private void addEdge(
233             Map<String, MavenProject> projectMap,
234             Map<String, Map<String, Vertex>> vertexMap,
235             MavenProject project,
236             Vertex projectVertex,
237             String groupId,
238             String artifactId,
239             String version,
240             boolean force,
241             boolean safe)
242             throws CycleDetectedException {
243         String projectKey = ArtifactUtils.versionlessKey(groupId, artifactId);
244 
245         Map<String, Vertex> vertices = vertexMap.get(projectKey);
246 
247         if (vertices != null) {
248             if (isSpecificVersion(version)) {
249                 Vertex vertex = vertices.get(version);
250                 if (vertex != null) {
251                     addEdge(projectVertex, vertex, project, projectMap, force, safe);
252                 }
253             } else {
254                 for (Vertex vertex : vertices.values()) {
255                     addEdge(projectVertex, vertex, project, projectMap, force, safe);
256                 }
257             }
258         }
259     }
260 
261     private void addEdge(
262             Vertex fromVertex,
263             Vertex toVertex,
264             MavenProject fromProject,
265             Map<String, MavenProject> projectMap,
266             boolean force,
267             boolean safe)
268             throws CycleDetectedException {
269         if (fromVertex.equals(toVertex)) {
270             return;
271         }
272 
273         if (fromProject != null) {
274             MavenProject toProject = projectMap.get(toVertex.getLabel());
275             fromProject.addProjectReference(toProject);
276         }
277 
278         if (force && toVertex.getChildren().contains(fromVertex)) {
279             dag.removeEdge(toVertex, fromVertex);
280         }
281 
282         try {
283             dag.addEdge(fromVertex, toVertex);
284         } catch (CycleDetectedException e) {
285             if (!safe) {
286                 throw e;
287             }
288         }
289     }
290 
291     private boolean isSpecificVersion(String version) {
292         return !(StringUtils.isEmpty(version) || version.startsWith("[") || version.startsWith("("));
293     }
294 
295     // TODO !![jc; 28-jul-2005] check this; if we're using '-r' and there are aggregator tasks, this will result in
296     // weirdness.
297     public MavenProject getTopLevelProject() {
298         if (topLevelProject == null) {
299             for (Iterator<MavenProject> i = sortedProjects.iterator(); i.hasNext() && (topLevelProject == null); ) {
300                 MavenProject project = i.next();
301                 if (project.isExecutionRoot()) {
302                     topLevelProject = project;
303                 }
304             }
305         }
306 
307         return topLevelProject;
308     }
309 
310     public List<MavenProject> getSortedProjects() {
311         return sortedProjects;
312     }
313 
314     public boolean hasMultipleProjects() {
315         return sortedProjects.size() > 1;
316     }
317 
318     public List<String> getDependents(String id) {
319         return dag.getParentLabels(id);
320     }
321 
322     public List<String> getDependencies(String id) {
323         return dag.getChildLabels(id);
324     }
325 
326     public static String getId(MavenProject project) {
327         return ArtifactUtils.key(project.getGroupId(), project.getArtifactId(), project.getVersion());
328     }
329 
330     public DAG getDAG() {
331         return dag;
332     }
333 
334     public Map<String, MavenProject> getProjectMap() {
335         return projectMap;
336     }
337 }