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