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.collector;
20  
21  import java.io.File;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.List;
25  import java.util.Objects;
26  import java.util.function.Predicate;
27  import javax.inject.Inject;
28  import javax.inject.Named;
29  import javax.inject.Singleton;
30  import org.apache.maven.execution.MavenExecutionRequest;
31  import org.apache.maven.model.Plugin;
32  import org.apache.maven.model.building.ModelProblem;
33  import org.apache.maven.model.locator.ModelLocator;
34  import org.apache.maven.plugin.PluginManagerException;
35  import org.apache.maven.plugin.PluginResolutionException;
36  import org.apache.maven.project.MavenProject;
37  import org.apache.maven.project.ProjectBuildingException;
38  import org.apache.maven.project.ProjectBuildingResult;
39  import org.eclipse.aether.resolution.ArtifactResolutionException;
40  import org.eclipse.aether.transfer.ArtifactNotFoundException;
41  import org.slf4j.Logger;
42  import org.slf4j.LoggerFactory;
43  
44  /**
45   * Strategy for collecting Maven projects from the multi-module project root, even when executed in a submodule.
46   */
47  @Named("MultiModuleCollectionStrategy")
48  @Singleton
49  public class MultiModuleCollectionStrategy implements ProjectCollectionStrategy {
50      private static final Logger LOGGER = LoggerFactory.getLogger(MultiModuleCollectionStrategy.class);
51      private final ModelLocator modelLocator;
52      private final ProjectsSelector projectsSelector;
53  
54      @Inject
55      public MultiModuleCollectionStrategy(ModelLocator modelLocator, ProjectsSelector projectsSelector) {
56          this.modelLocator = modelLocator;
57          this.projectsSelector = projectsSelector;
58      }
59  
60      @Override
61      public List<MavenProject> collectProjects(MavenExecutionRequest request) throws ProjectBuildingException {
62          File moduleProjectPomFile = getMultiModuleProjectPomFile(request);
63          List<File> files = Collections.singletonList(moduleProjectPomFile.getAbsoluteFile());
64          try {
65              List<MavenProject> projects = projectsSelector.selectProjects(files, request);
66              boolean isRequestedProjectCollected = isRequestedProjectCollected(request, projects);
67              if (isRequestedProjectCollected) {
68                  return projects;
69              } else {
70                  LOGGER.debug(
71                          "Multi module project collection failed:{}"
72                                  + "Detected a POM file next to a .mvn directory in a parent directory ({}). "
73                                  + "Maven assumed that POM file to be the parent of the requested project ({}), but it turned "
74                                  + "out that it was not. Another project collection strategy will be executed as result.",
75                          System.lineSeparator(),
76                          moduleProjectPomFile.getAbsolutePath(),
77                          request.getPom().getAbsolutePath());
78                  return Collections.emptyList();
79              }
80          } catch (ProjectBuildingException e) {
81              boolean fallThrough = isModuleOutsideRequestScopeDependingOnPluginModule(request, e);
82  
83              if (fallThrough) {
84                  LOGGER.debug(
85                          "Multi module project collection failed:{}"
86                                  + "Detected that one of the modules of this multi-module project uses another module as "
87                                  + "plugin extension which still needed to be built. This is not possible within the same "
88                                  + "reactor build. Another project collection strategy will be executed as result.",
89                          System.lineSeparator());
90                  return Collections.emptyList();
91              }
92  
93              throw e;
94          }
95      }
96  
97      private File getMultiModuleProjectPomFile(MavenExecutionRequest request) {
98          if (request.getPom().getParentFile().equals(request.getMultiModuleProjectDirectory())) {
99              return request.getPom();
100         } else {
101             File multiModuleProjectPom = modelLocator.locatePom(request.getMultiModuleProjectDirectory());
102             if (!multiModuleProjectPom.exists()) {
103                 LOGGER.info(
104                         "Maven detected that the requested POM file is part of a multi-module project, "
105                                 + "but could not find a pom.xml file in the multi-module root directory '{}'.",
106                         request.getMultiModuleProjectDirectory());
107                 LOGGER.info("The reactor is limited to all projects under: "
108                         + request.getPom().getParent());
109                 return request.getPom();
110             }
111 
112             return multiModuleProjectPom;
113         }
114     }
115 
116     /**
117      * multiModuleProjectDirectory in MavenExecutionRequest is not always the parent of the request pom.
118      * We should always check whether the request pom project is collected.
119      * The integration tests for MNG-6223 are examples for this scenario.
120      *
121      * @return true if the collected projects contain the requested project (for example with -f)
122      */
123     private boolean isRequestedProjectCollected(MavenExecutionRequest request, List<MavenProject> projects) {
124         return projects.stream().map(MavenProject::getFile).anyMatch(request.getPom()::equals);
125     }
126 
127     /**
128      * This method finds out whether collecting projects failed because of the following scenario:
129      * - A multi-module project containing a module which is a plugin and another module which depends on it.
130      * - Just the plugin is being built with the -f <pom> flag.
131      * - Because of inter-module dependency collection, all projects in the multi-module project are collected.
132      * - The plugin is not yet installed in a repository.
133      *
134      * Therefore the build fails because the plugin is not found and plugins cannot be built in the same session.
135      *
136      * The integration test for <a href="https://issues.apache.org/jira/browse/MNG-5572">MNG-5572</a> is an
137      *   example of this scenario.
138      *
139      * @return true if the module which fails to collect the inter-module plugin is not part of the build.
140      */
141     private boolean isModuleOutsideRequestScopeDependingOnPluginModule(
142             MavenExecutionRequest request, ProjectBuildingException exception) {
143         return exception.getResults().stream()
144                 .map(ProjectBuildingResult::getProject)
145                 .filter(Objects::nonNull)
146                 .filter(project -> request.getPom().equals(project.getFile()))
147                 .findFirst()
148                 .map(requestPomProject -> {
149                     List<MavenProject> modules = requestPomProject.getCollectedProjects() != null
150                             ? requestPomProject.getCollectedProjects()
151                             : Collections.emptyList();
152                     List<MavenProject> projectsInRequestScope = new ArrayList<>(modules);
153                     projectsInRequestScope.add(requestPomProject);
154 
155                     Predicate<ProjectBuildingResult> projectsOutsideOfRequestScope =
156                             pr -> !projectsInRequestScope.contains(pr.getProject());
157 
158                     Predicate<Exception> pluginArtifactNotFoundException = exc -> exc instanceof PluginManagerException
159                             && exc.getCause() instanceof PluginResolutionException
160                             && exc.getCause().getCause() instanceof ArtifactResolutionException
161                             && exc.getCause().getCause().getCause() instanceof ArtifactNotFoundException;
162 
163                     Predicate<Plugin> isPluginPartOfRequestScope = plugin -> projectsInRequestScope.stream()
164                             .anyMatch(project -> project.getGroupId().equals(plugin.getGroupId())
165                                     && project.getArtifactId().equals(plugin.getArtifactId())
166                                     && project.getVersion().equals(plugin.getVersion()));
167 
168                     return exception.getResults().stream()
169                             .filter(projectsOutsideOfRequestScope)
170                             .flatMap(projectBuildingResult -> projectBuildingResult.getProblems().stream())
171                             .map(ModelProblem::getException)
172                             .filter(pluginArtifactNotFoundException)
173                             .map(exc -> ((PluginResolutionException) exc.getCause()).getPlugin())
174                             .anyMatch(isPluginPartOfRequestScope);
175                 })
176                 .orElse(false);
177     }
178 }