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.lifecycle.internal.builder;
20  
21  import java.util.List;
22  import java.util.Optional;
23  import java.util.Properties;
24  import java.util.Set;
25  import java.util.stream.Collectors;
26  import javax.inject.Inject;
27  import javax.inject.Named;
28  import javax.inject.Singleton;
29  import org.apache.maven.artifact.Artifact;
30  import org.apache.maven.execution.BuildFailure;
31  import org.apache.maven.execution.ExecutionEvent;
32  import org.apache.maven.execution.MavenExecutionRequest;
33  import org.apache.maven.execution.MavenSession;
34  import org.apache.maven.feature.Features;
35  import org.apache.maven.internal.MultilineMessageHelper;
36  import org.apache.maven.lifecycle.LifecycleExecutionException;
37  import org.apache.maven.lifecycle.LifecycleNotFoundException;
38  import org.apache.maven.lifecycle.LifecyclePhaseNotFoundException;
39  import org.apache.maven.lifecycle.MavenExecutionPlan;
40  import org.apache.maven.lifecycle.internal.DefaultLifecyclePluginAnalyzer;
41  import org.apache.maven.lifecycle.internal.ExecutionEventCatapult;
42  import org.apache.maven.lifecycle.internal.LifecycleDebugLogger;
43  import org.apache.maven.lifecycle.internal.LifecycleExecutionPlanCalculator;
44  import org.apache.maven.lifecycle.internal.ReactorContext;
45  import org.apache.maven.lifecycle.internal.TaskSegment;
46  import org.apache.maven.model.Plugin;
47  import org.apache.maven.plugin.InvalidPluginDescriptorException;
48  import org.apache.maven.plugin.MojoExecution;
49  import org.apache.maven.plugin.MojoNotFoundException;
50  import org.apache.maven.plugin.PluginDescriptorParsingException;
51  import org.apache.maven.plugin.PluginNotFoundException;
52  import org.apache.maven.plugin.PluginResolutionException;
53  import org.apache.maven.plugin.descriptor.MojoDescriptor;
54  import org.apache.maven.plugin.prefix.NoPluginFoundForPrefixException;
55  import org.apache.maven.plugin.version.PluginVersionResolutionException;
56  import org.apache.maven.project.MavenProject;
57  import org.codehaus.plexus.classworlds.realm.ClassRealm;
58  import org.slf4j.Logger;
59  import org.slf4j.LoggerFactory;
60  
61  /**
62   * Common code that is shared by the LifecycleModuleBuilder and the LifeCycleWeaveBuilder
63   *
64   * @since 3.0
65   * @author Kristian Rosenvold
66   *         Builds one or more lifecycles for a full module
67   *         NOTE: This class is not part of any public api and can be changed or deleted without prior notice.
68   */
69  @Named
70  @Singleton
71  public class BuilderCommon {
72      private final Logger logger;
73      private final LifecycleDebugLogger lifecycleDebugLogger;
74      private final LifecycleExecutionPlanCalculator lifeCycleExecutionPlanCalculator;
75      private final ExecutionEventCatapult eventCatapult;
76  
77      @Inject
78      public BuilderCommon(
79              LifecycleDebugLogger lifecycleDebugLogger,
80              LifecycleExecutionPlanCalculator lifeCycleExecutionPlanCalculator,
81              ExecutionEventCatapult eventCatapult) {
82          this.logger = LoggerFactory.getLogger(getClass());
83          this.lifecycleDebugLogger = lifecycleDebugLogger;
84          this.lifeCycleExecutionPlanCalculator = lifeCycleExecutionPlanCalculator;
85          this.eventCatapult = eventCatapult;
86      }
87  
88      /**
89       * Ctor needed for UT.
90       */
91      BuilderCommon(
92              LifecycleDebugLogger lifecycleDebugLogger,
93              LifecycleExecutionPlanCalculator lifeCycleExecutionPlanCalculator,
94              ExecutionEventCatapult eventCatapult,
95              Logger logger) {
96          this.lifecycleDebugLogger = lifecycleDebugLogger;
97          this.lifeCycleExecutionPlanCalculator = lifeCycleExecutionPlanCalculator;
98          this.eventCatapult = eventCatapult;
99          this.logger = logger;
100     }
101 
102     public MavenExecutionPlan resolveBuildPlan(
103             MavenSession session, MavenProject project, TaskSegment taskSegment, Set<Artifact> projectArtifacts)
104             throws PluginNotFoundException, PluginResolutionException, LifecyclePhaseNotFoundException,
105                     PluginDescriptorParsingException, MojoNotFoundException, InvalidPluginDescriptorException,
106                     NoPluginFoundForPrefixException, LifecycleNotFoundException, PluginVersionResolutionException,
107                     LifecycleExecutionException {
108         MavenExecutionPlan executionPlan =
109                 lifeCycleExecutionPlanCalculator.calculateExecutionPlan(session, project, taskSegment.getTasks());
110 
111         lifecycleDebugLogger.debugProjectPlan(project, executionPlan);
112 
113         // With Maven 4's build/consumer the POM will always rewrite during distribution.
114         // The maven-gpg-plugin uses the original POM, causing an invalid signature.
115         // Fail as long as there's no solution available yet
116         Properties userProperties = session.getUserProperties();
117         if (Features.buildConsumer(userProperties).isActive()) {
118             Optional<MojoExecution> gpgMojo = executionPlan.getMojoExecutions().stream()
119                     .filter(m -> "maven-gpg-plugin".equals(m.getArtifactId())
120                             && "org.apache.maven.plugins".equals(m.getGroupId()))
121                     .findAny();
122 
123             if (gpgMojo.isPresent()) {
124                 throw new LifecycleExecutionException("The maven-gpg-plugin is not supported by Maven 4."
125                         + " Verify if there is a compatible signing solution,"
126                         + " add -D" + Features.buildConsumer(userProperties).propertyName() + "=false"
127                         + " or use Maven 3.");
128             }
129         }
130 
131         if (session.getRequest().getDegreeOfConcurrency() > 1
132                 && session.getProjects().size() > 1) {
133             final Set<Plugin> unsafePlugins = executionPlan.getNonThreadSafePlugins();
134             if (!unsafePlugins.isEmpty()) {
135                 for (String s : MultilineMessageHelper.format(
136                         "Your build is requesting parallel execution, but this project contains the following "
137                                 + "plugin(s) that have goals not marked as thread-safe to support parallel execution.",
138                         "While this /may/ work fine, please look for plugin updates and/or "
139                                 + "request plugins be made thread-safe.",
140                         "If reporting an issue, report it against the plugin in question, not against Apache Maven.")) {
141                     logger.warn(s);
142                 }
143                 if (logger.isDebugEnabled()) {
144                     final Set<MojoDescriptor> unsafeGoals = executionPlan.getNonThreadSafeMojos();
145                     logger.warn("The following goals are not marked as thread-safe in " + project.getName() + ":");
146                     for (MojoDescriptor unsafeGoal : unsafeGoals) {
147                         logger.warn("  " + unsafeGoal.getId());
148                     }
149                 } else {
150                     logger.warn("The following plugins are not marked as thread-safe in " + project.getName() + ":");
151                     for (Plugin unsafePlugin : unsafePlugins) {
152                         logger.warn("  " + unsafePlugin.getId());
153                     }
154                     logger.warn("");
155                     logger.warn("Enable verbose output (-X) to see precisely which goals are not marked as"
156                             + " thread-safe.");
157                 }
158                 logger.warn(MultilineMessageHelper.separatorLine());
159             }
160         }
161 
162         final String defaulModelId = DefaultLifecyclePluginAnalyzer.DEFAULTLIFECYCLEBINDINGS_MODELID;
163 
164         List<String> unversionedPlugins = executionPlan.getMojoExecutions().stream()
165                 .map(MojoExecution::getPlugin)
166                 .filter(p -> p.getLocation("version") != null
167                         && p.getLocation("version").getSource() != null
168                         && defaulModelId.equals(
169                                 p.getLocation("version").getSource().getModelId()))
170                 .distinct()
171                 .map(Plugin::getArtifactId) // managed by us, groupId is always o.a.m.plugins
172                 .collect(Collectors.toList());
173 
174         if (!unversionedPlugins.isEmpty()) {
175             logger.warn("Version not locked for default bindings plugins " + unversionedPlugins
176                     + ", you should define versions in pluginManagement section of your " + "pom.xml or parent");
177         }
178 
179         return executionPlan;
180     }
181 
182     public void handleBuildError(
183             final ReactorContext buildContext,
184             final MavenSession rootSession,
185             final MavenSession currentSession,
186             final MavenProject mavenProject,
187             Throwable t,
188             final long buildStartTime) {
189         // record the error and mark the project as failed
190         long buildEndTime = System.currentTimeMillis();
191         buildContext.getResult().addException(t);
192         buildContext.getResult().addBuildSummary(new BuildFailure(mavenProject, buildEndTime - buildStartTime, t));
193 
194         // notify listeners about "soft" project build failures only
195         if (t instanceof Exception && !(t instanceof RuntimeException)) {
196             eventCatapult.fire(ExecutionEvent.Type.ProjectFailed, currentSession, null, (Exception) t);
197         }
198 
199         // reactor failure modes
200         if (t instanceof RuntimeException || !(t instanceof Exception)) {
201             // fail fast on RuntimeExceptions, Errors and "other" Throwables
202             // assume these are system errors and further build is meaningless
203             buildContext.getReactorBuildStatus().halt();
204         } else if (MavenExecutionRequest.REACTOR_FAIL_NEVER.equals(rootSession.getReactorFailureBehavior())) {
205             // continue the build
206         } else if (MavenExecutionRequest.REACTOR_FAIL_AT_END.equals(rootSession.getReactorFailureBehavior())) {
207             // continue the build but ban all projects that depend on the failed one
208             buildContext.getReactorBuildStatus().blackList(mavenProject);
209         } else if (MavenExecutionRequest.REACTOR_FAIL_FAST.equals(rootSession.getReactorFailureBehavior())) {
210             buildContext.getReactorBuildStatus().halt();
211         } else {
212             logger.error("invalid reactor failure behavior " + rootSession.getReactorFailureBehavior());
213             buildContext.getReactorBuildStatus().halt();
214         }
215     }
216 
217     public static void attachToThread(MavenProject currentProject) {
218         ClassRealm projectRealm = currentProject.getClassRealm();
219         if (projectRealm != null) {
220             Thread.currentThread().setContextClassLoader(projectRealm);
221         }
222     }
223 
224     // TODO I'm really wondering where this method belongs; smells like it should be on MavenProject, but for some
225     // reason it isn't ? This localization is kind-of a code smell.
226 
227     public static String getKey(MavenProject project) {
228         return project.getGroupId() + ':' + project.getArtifactId() + ':' + project.getVersion();
229     }
230 }