001package org.apache.maven.lifecycle.internal;
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.artifact.resolver.filter.ArtifactFilter;
024import org.apache.maven.artifact.resolver.filter.CumulativeScopeArtifactFilter;
025import org.apache.maven.execution.ExecutionEvent;
026import org.apache.maven.execution.MavenSession;
027import org.apache.maven.lifecycle.LifecycleExecutionException;
028import org.apache.maven.lifecycle.MissingProjectException;
029import org.apache.maven.plugin.BuildPluginManager;
030import org.apache.maven.plugin.MavenPluginManager;
031import org.apache.maven.plugin.MojoExecution;
032import org.apache.maven.plugin.MojoExecutionException;
033import org.apache.maven.plugin.MojoFailureException;
034import org.apache.maven.plugin.PluginConfigurationException;
035import org.apache.maven.plugin.PluginIncompatibleException;
036import org.apache.maven.plugin.PluginManagerException;
037import org.apache.maven.plugin.descriptor.MojoDescriptor;
038import org.apache.maven.project.MavenProject;
039import org.codehaus.plexus.component.annotations.Component;
040import org.codehaus.plexus.component.annotations.Requirement;
041import org.codehaus.plexus.util.StringUtils;
042
043import java.util.ArrayList;
044import java.util.Arrays;
045import java.util.Collection;
046import java.util.Collections;
047import java.util.List;
048import java.util.Map;
049import java.util.Set;
050import java.util.TreeSet;
051
052/**
053 * Executes an individual mojo
054 *
055 * @author Jason van Zyl
056 * @author Benjamin Bentmann
057 * @author Kristian Rosenvold
058 *         <p/>
059 *         NOTE: This class is not part of any public api and can be changed or deleted without prior notice.
060 * @since 3.0
061 */
062@Component( role = MojoExecutor.class )
063public class MojoExecutor
064{
065
066    @Requirement
067    private BuildPluginManager pluginManager;
068
069    @Requirement
070    private MavenPluginManager mavenPluginManager;
071
072    @Requirement
073    private LifecycleDependencyResolver lifeCycleDependencyResolver;
074
075    @Requirement
076    private ExecutionEventCatapult eventCatapult;
077
078    public MojoExecutor()
079    {
080    }
081
082    public DependencyContext newDependencyContext( MavenSession session, List<MojoExecution> mojoExecutions )
083    {
084        Set<String> scopesToCollect = new TreeSet<>();
085        Set<String> scopesToResolve = new TreeSet<>();
086
087        collectDependencyRequirements( scopesToResolve, scopesToCollect, mojoExecutions );
088
089        return new DependencyContext( session.getCurrentProject(), scopesToCollect, scopesToResolve );
090    }
091
092    private void collectDependencyRequirements( Set<String> scopesToResolve, Set<String> scopesToCollect,
093                                                Collection<MojoExecution> mojoExecutions )
094    {
095        for ( MojoExecution mojoExecution : mojoExecutions )
096        {
097            MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
098
099            scopesToResolve.addAll( toScopes( mojoDescriptor.getDependencyResolutionRequired() ) );
100
101            scopesToCollect.addAll( toScopes( mojoDescriptor.getDependencyCollectionRequired() ) );
102        }
103    }
104
105    private Collection<String> toScopes( String classpath )
106    {
107        if ( StringUtils.isNotEmpty( classpath ) )
108        {
109            if ( Artifact.SCOPE_COMPILE.equals( classpath ) )
110            {
111                return Arrays.asList( Artifact.SCOPE_COMPILE, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_PROVIDED );
112            }
113            else if ( Artifact.SCOPE_RUNTIME.equals( classpath ) )
114            {
115                return Arrays.asList( Artifact.SCOPE_COMPILE, Artifact.SCOPE_RUNTIME );
116            }
117            else if ( Artifact.SCOPE_COMPILE_PLUS_RUNTIME.equals( classpath ) )
118            {
119                return Arrays.asList( Artifact.SCOPE_COMPILE, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_PROVIDED,
120                                      Artifact.SCOPE_RUNTIME );
121            }
122            else if ( Artifact.SCOPE_RUNTIME_PLUS_SYSTEM.equals( classpath ) )
123            {
124                return Arrays.asList( Artifact.SCOPE_COMPILE, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_RUNTIME );
125            }
126            else if ( Artifact.SCOPE_TEST.equals( classpath ) )
127            {
128                return Arrays.asList( Artifact.SCOPE_COMPILE, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_PROVIDED,
129                                      Artifact.SCOPE_RUNTIME, Artifact.SCOPE_TEST );
130            }
131        }
132        return Collections.emptyList();
133    }
134
135    public void execute( MavenSession session, List<MojoExecution> mojoExecutions, ProjectIndex projectIndex )
136        throws LifecycleExecutionException
137
138    {
139        DependencyContext dependencyContext = newDependencyContext( session, mojoExecutions );
140
141        PhaseRecorder phaseRecorder = new PhaseRecorder( session.getCurrentProject() );
142
143        for ( MojoExecution mojoExecution : mojoExecutions )
144        {
145            execute( session, mojoExecution, projectIndex, dependencyContext, phaseRecorder );
146        }
147    }
148
149    public void execute( MavenSession session, MojoExecution mojoExecution, ProjectIndex projectIndex,
150                         DependencyContext dependencyContext, PhaseRecorder phaseRecorder )
151        throws LifecycleExecutionException
152    {
153        execute( session, mojoExecution, projectIndex, dependencyContext );
154        phaseRecorder.observeExecution( mojoExecution );
155    }
156
157    private void execute( MavenSession session, MojoExecution mojoExecution, ProjectIndex projectIndex,
158                          DependencyContext dependencyContext )
159        throws LifecycleExecutionException
160    {
161        MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
162
163        try
164        {
165            mavenPluginManager.checkRequiredMavenVersion( mojoDescriptor.getPluginDescriptor() );
166        }
167        catch ( PluginIncompatibleException e )
168        {
169            throw new LifecycleExecutionException( mojoExecution, session.getCurrentProject(), e );
170        }
171
172        if ( mojoDescriptor.isProjectRequired() && !session.getRequest().isProjectPresent() )
173        {
174            Throwable cause = new MissingProjectException(
175                "Goal requires a project to execute" + " but there is no POM in this directory ("
176                    + session.getExecutionRootDirectory() + ")."
177                    + " Please verify you invoked Maven from the correct directory." );
178            throw new LifecycleExecutionException( mojoExecution, null, cause );
179        }
180
181        if ( mojoDescriptor.isOnlineRequired() && session.isOffline() )
182        {
183            if ( MojoExecution.Source.CLI.equals( mojoExecution.getSource() ) )
184            {
185                Throwable cause = new IllegalStateException(
186                    "Goal requires online mode for execution" + " but Maven is currently offline." );
187                throw new LifecycleExecutionException( mojoExecution, session.getCurrentProject(), cause );
188            }
189            else
190            {
191                eventCatapult.fire( ExecutionEvent.Type.MojoSkipped, session, mojoExecution );
192
193                return;
194            }
195        }
196
197        List<MavenProject> forkedProjects = executeForkedExecutions( mojoExecution, session, projectIndex );
198
199        ensureDependenciesAreResolved( mojoDescriptor, session, dependencyContext );
200
201        eventCatapult.fire( ExecutionEvent.Type.MojoStarted, session, mojoExecution );
202
203        try
204        {
205            try
206            {
207                pluginManager.executeMojo( session, mojoExecution );
208            }
209            catch ( MojoFailureException | PluginManagerException | PluginConfigurationException
210                | MojoExecutionException e )
211            {
212                throw new LifecycleExecutionException( mojoExecution, session.getCurrentProject(), e );
213            }
214
215            eventCatapult.fire( ExecutionEvent.Type.MojoSucceeded, session, mojoExecution );
216        }
217        catch ( LifecycleExecutionException e )
218        {
219            eventCatapult.fire( ExecutionEvent.Type.MojoFailed, session, mojoExecution, e );
220
221            throw e;
222        }
223        finally
224        {
225            for ( MavenProject forkedProject : forkedProjects )
226            {
227                forkedProject.setExecutionProject( null );
228            }
229        }
230    }
231
232    public void ensureDependenciesAreResolved( MojoDescriptor mojoDescriptor, MavenSession session,
233                                               DependencyContext dependencyContext )
234        throws LifecycleExecutionException
235
236    {
237        MavenProject project = dependencyContext.getProject();
238        boolean aggregating = mojoDescriptor.isAggregator();
239
240        if ( dependencyContext.isResolutionRequiredForCurrentProject() )
241        {
242            Collection<String> scopesToCollect = dependencyContext.getScopesToCollectForCurrentProject();
243            Collection<String> scopesToResolve = dependencyContext.getScopesToResolveForCurrentProject();
244
245            lifeCycleDependencyResolver.resolveProjectDependencies( project, scopesToCollect, scopesToResolve, session,
246                                                                    aggregating, Collections.<Artifact>emptySet() );
247
248            dependencyContext.synchronizeWithProjectState();
249        }
250
251        if ( aggregating )
252        {
253            Collection<String> scopesToCollect = toScopes( mojoDescriptor.getDependencyCollectionRequired() );
254            Collection<String> scopesToResolve = toScopes( mojoDescriptor.getDependencyResolutionRequired() );
255
256            if ( dependencyContext.isResolutionRequiredForAggregatedProjects( scopesToCollect, scopesToResolve ) )
257            {
258                for ( MavenProject aggregatedProject : session.getProjects() )
259                {
260                    if ( aggregatedProject != project )
261                    {
262                        lifeCycleDependencyResolver.resolveProjectDependencies( aggregatedProject, scopesToCollect,
263                                                                                scopesToResolve, session, aggregating,
264                                                                                Collections.<Artifact>emptySet() );
265                    }
266                }
267            }
268        }
269
270        ArtifactFilter artifactFilter = getArtifactFilter( mojoDescriptor );
271        List<MavenProject> projectsToResolve =
272            LifecycleDependencyResolver.getProjects( session.getCurrentProject(), session,
273                                                     mojoDescriptor.isAggregator() );
274        for ( MavenProject projectToResolve : projectsToResolve )
275        {
276            projectToResolve.setArtifactFilter( artifactFilter );
277        }
278    }
279
280    private ArtifactFilter getArtifactFilter( MojoDescriptor mojoDescriptor )
281    {
282        String scopeToResolve = mojoDescriptor.getDependencyResolutionRequired();
283        String scopeToCollect = mojoDescriptor.getDependencyCollectionRequired();
284
285        List<String> scopes = new ArrayList<>( 2 );
286        if ( StringUtils.isNotEmpty( scopeToCollect ) )
287        {
288            scopes.add( scopeToCollect );
289        }
290        if ( StringUtils.isNotEmpty( scopeToResolve ) )
291        {
292            scopes.add( scopeToResolve );
293        }
294
295        if ( scopes.isEmpty() )
296        {
297            return null;
298        }
299        else
300        {
301            return new CumulativeScopeArtifactFilter( scopes );
302        }
303    }
304
305    public List<MavenProject> executeForkedExecutions( MojoExecution mojoExecution, MavenSession session,
306                                                       ProjectIndex projectIndex )
307        throws LifecycleExecutionException
308    {
309        List<MavenProject> forkedProjects = Collections.emptyList();
310
311        Map<String, List<MojoExecution>> forkedExecutions = mojoExecution.getForkedExecutions();
312
313        if ( !forkedExecutions.isEmpty() )
314        {
315            eventCatapult.fire( ExecutionEvent.Type.ForkStarted, session, mojoExecution );
316
317            MavenProject project = session.getCurrentProject();
318
319            forkedProjects = new ArrayList<>( forkedExecutions.size() );
320
321            try
322            {
323                for ( Map.Entry<String, List<MojoExecution>> fork : forkedExecutions.entrySet() )
324                {
325                    String projectId = fork.getKey();
326
327                    int index = projectIndex.getIndices().get( projectId );
328
329                    MavenProject forkedProject = projectIndex.getProjects().get( projectId );
330
331                    forkedProjects.add( forkedProject );
332
333                    MavenProject executedProject = forkedProject.clone();
334
335                    forkedProject.setExecutionProject( executedProject );
336
337                    List<MojoExecution> mojoExecutions = fork.getValue();
338
339                    if ( mojoExecutions.isEmpty() )
340                    {
341                        continue;
342                    }
343
344                    try
345                    {
346                        session.setCurrentProject( executedProject );
347                        session.getProjects().set( index, executedProject );
348                        projectIndex.getProjects().put( projectId, executedProject );
349
350                        eventCatapult.fire( ExecutionEvent.Type.ForkedProjectStarted, session, mojoExecution );
351
352                        execute( session, mojoExecutions, projectIndex );
353
354                        eventCatapult.fire( ExecutionEvent.Type.ForkedProjectSucceeded, session, mojoExecution );
355                    }
356                    catch ( LifecycleExecutionException e )
357                    {
358                        eventCatapult.fire( ExecutionEvent.Type.ForkedProjectFailed, session, mojoExecution, e );
359
360                        throw e;
361                    }
362                    finally
363                    {
364                        projectIndex.getProjects().put( projectId, forkedProject );
365                        session.getProjects().set( index, forkedProject );
366                        session.setCurrentProject( project );
367                    }
368                }
369
370                eventCatapult.fire( ExecutionEvent.Type.ForkSucceeded, session, mojoExecution );
371            }
372            catch ( LifecycleExecutionException e )
373            {
374                eventCatapult.fire( ExecutionEvent.Type.ForkFailed, session, mojoExecution, e );
375
376                throw e;
377            }
378        }
379
380        return forkedProjects;
381    }
382}