001package org.apache.maven.lifecycle.internal.builder;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import org.apache.maven.artifact.Artifact;
023import org.apache.maven.execution.BuildFailure;
024import org.apache.maven.execution.ExecutionEvent;
025import org.apache.maven.execution.MavenExecutionRequest;
026import org.apache.maven.execution.MavenSession;
027import org.apache.maven.lifecycle.LifecycleExecutionException;
028import org.apache.maven.lifecycle.LifecycleNotFoundException;
029import org.apache.maven.lifecycle.LifecyclePhaseNotFoundException;
030import org.apache.maven.lifecycle.MavenExecutionPlan;
031import org.apache.maven.lifecycle.internal.ExecutionEventCatapult;
032import org.apache.maven.lifecycle.internal.LifecycleDebugLogger;
033import org.apache.maven.lifecycle.internal.LifecycleExecutionPlanCalculator;
034import org.apache.maven.lifecycle.internal.ReactorContext;
035import org.apache.maven.lifecycle.internal.TaskSegment;
036import org.apache.maven.model.Plugin;
037import org.apache.maven.plugin.InvalidPluginDescriptorException;
038import org.apache.maven.plugin.MojoNotFoundException;
039import org.apache.maven.plugin.PluginDescriptorParsingException;
040import org.apache.maven.plugin.PluginNotFoundException;
041import org.apache.maven.plugin.PluginResolutionException;
042import org.apache.maven.plugin.descriptor.MojoDescriptor;
043import org.apache.maven.plugin.prefix.NoPluginFoundForPrefixException;
044import org.apache.maven.plugin.version.PluginVersionResolutionException;
045import org.apache.maven.project.MavenProject;
046import org.codehaus.plexus.classworlds.realm.ClassRealm;
047import org.codehaus.plexus.component.annotations.Component;
048import org.codehaus.plexus.component.annotations.Requirement;
049import org.codehaus.plexus.logging.Logger;
050
051import java.util.Set;
052
053/**
054 * Common code that is shared by the LifecycleModuleBuilder and the LifeCycleWeaveBuilder
055 *
056 * @since 3.0
057 * @author Kristian Rosenvold
058 *         Builds one or more lifecycles for a full module
059 *         NOTE: This class is not part of any public api and can be changed or deleted without prior notice.
060 */
061@Component( role = BuilderCommon.class )
062public class BuilderCommon
063{
064    @Requirement
065    private LifecycleDebugLogger lifecycleDebugLogger;
066
067    @Requirement
068    private LifecycleExecutionPlanCalculator lifeCycleExecutionPlanCalculator;
069
070    @Requirement
071    private ExecutionEventCatapult eventCatapult;
072
073    @Requirement
074    private Logger logger;
075
076
077    public BuilderCommon()
078    {
079    }
080
081    public BuilderCommon( LifecycleDebugLogger lifecycleDebugLogger,
082                          LifecycleExecutionPlanCalculator lifeCycleExecutionPlanCalculator, Logger logger )
083    {
084        this.lifecycleDebugLogger = lifecycleDebugLogger;
085        this.lifeCycleExecutionPlanCalculator = lifeCycleExecutionPlanCalculator;
086        this.logger = logger;
087    }
088
089    public MavenExecutionPlan resolveBuildPlan( MavenSession session, MavenProject project, TaskSegment taskSegment,
090                                                Set<Artifact> projectArtifacts )
091        throws PluginNotFoundException, PluginResolutionException, LifecyclePhaseNotFoundException,
092        PluginDescriptorParsingException, MojoNotFoundException, InvalidPluginDescriptorException,
093        NoPluginFoundForPrefixException, LifecycleNotFoundException, PluginVersionResolutionException,
094        LifecycleExecutionException
095    {
096        MavenExecutionPlan executionPlan =
097            lifeCycleExecutionPlanCalculator.calculateExecutionPlan( session, project, taskSegment.getTasks() );
098
099        lifecycleDebugLogger.debugProjectPlan( project, executionPlan );
100
101        if ( session.getRequest().getDegreeOfConcurrency() > 1 )
102        {
103            final Set<Plugin> unsafePlugins = executionPlan.getNonThreadSafePlugins();
104            if ( !unsafePlugins.isEmpty()  && logger.isDebugEnabled() )
105            {
106                logger.warn( "*****************************************************************" );
107                logger.warn( "* Your build is requesting parallel execution, but project      *" );
108                logger.warn( "* contains the following plugin(s) that have goals not marked   *" );
109                logger.warn( "* as @threadSafe to support parallel building.                  *" );
110                logger.warn( "* While this /may/ work fine, please look for plugin updates    *" );
111                logger.warn( "* and/or request plugins be made thread-safe.                   *" );
112                logger.warn( "* If reporting an issue, report it against the plugin in        *" );
113                logger.warn( "* question, not against maven-core                              *" );
114                logger.warn( "*****************************************************************" );
115                if ( logger.isDebugEnabled() )
116                {
117                    final Set<MojoDescriptor> unsafeGoals = executionPlan.getNonThreadSafeMojos();
118                    logger.warn( "The following goals are not marked @threadSafe in " + project.getName() + ":" );
119                    for ( MojoDescriptor unsafeGoal : unsafeGoals )
120                    {
121                        logger.warn( unsafeGoal.getId() );
122                    }
123                }
124                else
125                {
126                    logger.warn( "The following plugins are not marked @threadSafe in " + project.getName() + ":" );
127                    for ( Plugin unsafePlugin : unsafePlugins )
128                    {
129                        logger.warn( unsafePlugin.getId() );
130                    }
131                    logger.warn( "Enable debug to see more precisely which goals are not marked @threadSafe." );
132                }
133                logger.warn( "*****************************************************************" );
134            }
135        }
136
137        return executionPlan;
138    }
139
140    public void handleBuildError( final ReactorContext buildContext, final MavenSession rootSession,
141                                  final MavenSession currentSession, final MavenProject mavenProject, Throwable t,
142                                  final long buildStartTime )
143    {
144        // record the error and mark the project as failed
145        long buildEndTime = System.currentTimeMillis();
146        buildContext.getResult().addException( t );
147        buildContext.getResult().addBuildSummary( new BuildFailure( mavenProject, buildEndTime - buildStartTime, t ) );
148
149        // notify listeners about "soft" project build failures only
150        if ( t instanceof Exception && !( t instanceof RuntimeException ) )
151        {
152            eventCatapult.fire( ExecutionEvent.Type.ProjectFailed, currentSession, null, (Exception) t );
153        }
154
155        // reactor failure modes
156        if ( t instanceof RuntimeException || !( t instanceof Exception ) )
157        {
158            // fail fast on RuntimeExceptions, Errors and "other" Throwables
159            // assume these are system errors and further build is meaningless
160            buildContext.getReactorBuildStatus().halt();
161        }
162        else if ( MavenExecutionRequest.REACTOR_FAIL_NEVER.equals( rootSession.getReactorFailureBehavior() ) )
163        {
164            // continue the build
165        }
166        else if ( MavenExecutionRequest.REACTOR_FAIL_AT_END.equals( rootSession.getReactorFailureBehavior() ) )
167        {
168            // continue the build but ban all projects that depend on the failed one
169            buildContext.getReactorBuildStatus().blackList( mavenProject );
170        }
171        else if ( MavenExecutionRequest.REACTOR_FAIL_FAST.equals( rootSession.getReactorFailureBehavior() ) )
172        {
173            buildContext.getReactorBuildStatus().halt();
174        }
175        else
176        {
177            logger.error( "invalid reactor failure behavior " + rootSession.getReactorFailureBehavior() );
178            buildContext.getReactorBuildStatus().halt();
179        }
180    }
181
182    public static void attachToThread( MavenProject currentProject )
183    {
184        ClassRealm projectRealm = currentProject.getClassRealm();
185        if ( projectRealm != null )
186        {
187            Thread.currentThread().setContextClassLoader( projectRealm );
188        }
189    }
190
191    // Todo: I'm really wondering where this method belongs; smells like it should be on MavenProject, but for some
192    // reason it isn't ? This localization is kind-of a code smell.
193
194    public static String getKey( MavenProject project )
195    {
196        return project.getGroupId() + ':' + project.getArtifactId() + ':' + project.getVersion();
197    }
198
199}