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