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