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