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         File multiModuleProjectDirectory = request.getMultiModuleProjectDirectory();
101         if (request.getPom().getParentFile().equals(multiModuleProjectDirectory)) {
102             return request.getPom();
103         } else {
104             File multiModuleProjectPom = modelLocator.locateExistingPom(multiModuleProjectDirectory);
105             if (multiModuleProjectPom == null) {
106                 LOGGER.info(
107                         "Maven detected that the requested POM file is part of a multi-module project, "
108                                 + "but could not find a pom.xml file in the multi-module root directory '{}'.",
109                         multiModuleProjectDirectory);
110                 LOGGER.info(
111                         "The reactor is limited to all projects under: {}",
112                         request.getPom().getParent());
113                 return request.getPom();
114             }
115 
116             return multiModuleProjectPom;
117         }
118     }
119 
120     /**
121      * multiModuleProjectDirectory in MavenExecutionRequest is not always the parent of the request pom.
122      * We should always check whether the request pom project is collected.
123      * The integration tests for MNG-6223 are examples for this scenario.
124      *
125      * @return true if the collected projects contain the requested project (for example with -f)
126      */
127     private boolean isRequestedProjectCollected(MavenExecutionRequest request, List<MavenProject> projects) {
128         return projects.stream().map(MavenProject::getFile).anyMatch(request.getPom()::equals);
129     }
130 
131     /**
132      * This method finds out whether collecting projects failed because of the following scenario:
133      * - A multi-module project containing a module which is a plugin and another module which depends on it.
134      * - Just the plugin is being built with the -f <pom> flag.
135      * - Because of inter-module dependency collection, all projects in the multi-module project are collected.
136      * - The plugin is not yet installed in a repository.
137      *
138      * Therefore the build fails because the plugin is not found and plugins cannot be built in the same session.
139      *
140      * The integration test for <a href="https://issues.apache.org/jira/browse/MNG-5572">MNG-5572</a> is an
141      *   example of this scenario.
142      *
143      * @return true if the module which fails to collect the inter-module plugin is not part of the build.
144      */
145     private boolean isModuleOutsideRequestScopeDependingOnPluginModule(
146             MavenExecutionRequest request, ProjectBuildingException exception) {
147         return exception.getResults().stream()
148                 .map(ProjectBuildingResult::getProject)
149                 .filter(Objects::nonNull)
150                 .filter(project -> request.getPom().equals(project.getFile()))
151                 .findFirst()
152                 .map(requestPomProject -> {
153                     List<MavenProject> modules = requestPomProject.getCollectedProjects() != null
154                             ? requestPomProject.getCollectedProjects()
155                             : Collections.emptyList();
156                     List<MavenProject> projectsInRequestScope = new ArrayList<>(modules);
157                     projectsInRequestScope.add(requestPomProject);
158 
159                     Predicate<ProjectBuildingResult> projectsOutsideOfRequestScope =
160                             pr -> !projectsInRequestScope.contains(pr.getProject());
161 
162                     Predicate<Exception> pluginArtifactNotFoundException = exc -> exc instanceof PluginManagerException
163                             && exc.getCause() instanceof PluginResolutionException
164                             && exc.getCause().getCause() instanceof ArtifactResolutionException
165                             && exc.getCause().getCause().getCause() instanceof ArtifactNotFoundException;
166 
167                     Predicate<Plugin> isPluginPartOfRequestScope = plugin -> projectsInRequestScope.stream()
168                             .anyMatch(project -> project.getGroupId().equals(plugin.getGroupId())
169                                     && project.getArtifactId().equals(plugin.getArtifactId())
170                                     && project.getVersion().equals(plugin.getVersion()));
171 
172                     return exception.getResults().stream()
173                             .filter(projectsOutsideOfRequestScope)
174                             .flatMap(projectBuildingResult -> projectBuildingResult.getProblems().stream())
175                             .map(ModelProblem::getException)
176                             .filter(pluginArtifactNotFoundException)
177                             .map(exc -> ((PluginResolutionException) exc.getCause()).getPlugin())
178                             .anyMatch(isPluginPartOfRequestScope);
179                 })
180                 .orElse(false);
181     }
182 }