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.ArtifactUtils;
024import org.apache.maven.execution.BuildSuccess;
025import org.apache.maven.execution.ExecutionEvent;
026import org.apache.maven.execution.MavenExecutionRequest;
027import org.apache.maven.execution.MavenSession;
028import org.apache.maven.lifecycle.LifecycleExecutionException;
029import org.apache.maven.lifecycle.MavenExecutionPlan;
030import org.apache.maven.lifecycle.Schedule;
031import org.apache.maven.project.MavenProject;
032import org.codehaus.plexus.component.annotations.Component;
033import org.codehaus.plexus.component.annotations.Requirement;
034import org.codehaus.plexus.logging.Logger;
035
036import java.util.ArrayList;
037import java.util.Collection;
038import java.util.HashMap;
039import java.util.HashSet;
040import java.util.Iterator;
041import java.util.List;
042import java.util.Map;
043import java.util.Properties;
044import java.util.Set;
045import java.util.concurrent.Callable;
046import java.util.concurrent.CompletionService;
047import java.util.concurrent.ExecutionException;
048import java.util.concurrent.ExecutorCompletionService;
049import java.util.concurrent.ExecutorService;
050import java.util.concurrent.Future;
051
052/**
053 * Builds the full lifecycle in weave-mode (phase by phase as opposed to project-by-project)
054 * <p/>
055 * NOTE: Weave mode is still experimental. It may be either promoted to first class citizen
056 * at some later point in time, and it may also be removed entirely. Weave mode has much more aggressive
057 * concurrency behaviour than regular threaded mode, and as such is still under test wrt cross platform stability.
058 * <p/>
059 * To remove weave mode from m3, the following should be removed:
060 * ExecutionPlanItem.schedule w/setters and getters
061 * DefaultLifeCycles.getScheduling() and all its use
062 * ReactorArtifactRepository has a reference to isWeave too.
063 * This class and its usage
064 * 
065 * @since 3.0
066 * @author Kristian Rosenvold
067 *         Builds one or more lifecycles for a full module
068 *         <p/>
069 *         NOTE: This class is not part of any public api and can be changed or deleted without prior notice.
070 */
071@Component( role = LifecycleWeaveBuilder.class )
072public class LifecycleWeaveBuilder
073{
074
075    @Requirement
076    private MojoExecutor mojoExecutor;
077
078    @Requirement
079    private BuilderCommon builderCommon;
080
081    @Requirement
082    private Logger logger;
083
084    @Requirement
085    private ExecutionEventCatapult eventCatapult;
086
087    private Map<MavenProject, MavenExecutionPlan> executionPlans = new HashMap<MavenProject, MavenExecutionPlan>();
088
089
090    public LifecycleWeaveBuilder()
091    {
092    }
093
094    public LifecycleWeaveBuilder( MojoExecutor mojoExecutor, BuilderCommon builderCommon, Logger logger,
095                                  ExecutionEventCatapult eventCatapult )
096    {
097        this.mojoExecutor = mojoExecutor;
098        this.builderCommon = builderCommon;
099        this.logger = logger;
100        this.eventCatapult = eventCatapult;
101    }
102
103    public void build( ProjectBuildList projectBuilds, ReactorContext buildContext, List<TaskSegment> taskSegments,
104                       MavenSession session, ExecutorService executor, ReactorBuildStatus reactorBuildStatus )
105        throws ExecutionException, InterruptedException
106    {
107        ConcurrentBuildLogger concurrentBuildLogger = new ConcurrentBuildLogger();
108        CompletionService<ProjectSegment> service = new ExecutorCompletionService<ProjectSegment>( executor );
109
110        try
111        {
112            for ( MavenProject mavenProject : session.getProjects() )
113            {
114                Artifact mainArtifact = mavenProject.getArtifact();
115                if ( mainArtifact != null && !( mainArtifact instanceof ThreadLockedArtifact ) )
116                {
117                    ThreadLockedArtifact threadLockedArtifact = new ThreadLockedArtifact( mainArtifact );
118                    mavenProject.setArtifact( threadLockedArtifact );
119                }
120            }
121
122            final List<Future<ProjectSegment>> futures = new ArrayList<Future<ProjectSegment>>();
123            final Map<ProjectSegment, Future<MavenExecutionPlan>> plans =
124                new HashMap<ProjectSegment, Future<MavenExecutionPlan>>();
125
126            for ( TaskSegment taskSegment : taskSegments )
127            {
128                ProjectBuildList segmentChunks = projectBuilds.getByTaskSegment( taskSegment );
129                Set<Artifact> projectArtifacts = new HashSet<Artifact>();
130                for ( ProjectSegment segmentChunk : segmentChunks )
131                {
132                    Artifact artifact = segmentChunk.getProject().getArtifact();
133                    if ( artifact != null )
134                    {
135                        projectArtifacts.add( artifact );
136                    }
137                }
138                for ( ProjectSegment projectBuild : segmentChunks )
139                {
140                    plans.put( projectBuild, executor.submit( createEPFuture( projectBuild, projectArtifacts ) ) );
141                }
142
143                for ( ProjectSegment projectSegment : plans.keySet() )
144                {
145                    executionPlans.put( projectSegment.getProject(), plans.get( projectSegment ).get() );
146
147                }
148                for ( ProjectSegment projectBuild : segmentChunks )
149                {
150                    try
151                    {
152                        final MavenExecutionPlan executionPlan = plans.get( projectBuild ).get();
153
154                        DependencyContext dependencyContext =
155                            mojoExecutor.newDependencyContext( session, executionPlan.getMojoExecutions() );
156
157                        final Callable<ProjectSegment> projectBuilder =
158                            createCallableForBuildingOneFullModule( buildContext, session, reactorBuildStatus,
159                                                                    executionPlan, projectBuild, dependencyContext,
160                                                                    concurrentBuildLogger );
161
162                        futures.add( service.submit( projectBuilder ) );
163                    }
164                    catch ( Exception e )
165                    {
166                        throw new ExecutionException( e );
167                    }
168                }
169
170                for ( Future<ProjectSegment> buildFuture : futures )
171                {
172                    buildFuture.get();  // At this point, this build *is* finished.
173                    // Do not leak threads past here or evil gremlins will get you!
174                }
175                futures.clear();
176            }
177        }
178        finally
179        {
180            projectBuilds.closeAll();
181        }
182        logger.info( concurrentBuildLogger.toString() );
183    }
184
185    private Callable<MavenExecutionPlan> createEPFuture( final ProjectSegment projectSegment,
186                                                         final Set<Artifact> projectArtifacts )
187    {
188        return new Callable<MavenExecutionPlan>()
189        {
190            public MavenExecutionPlan call()
191                throws Exception
192            {
193                return builderCommon.resolveBuildPlan( projectSegment.getSession(), projectSegment.getProject(),
194                                                       projectSegment.getTaskSegment(), projectArtifacts );
195            }
196        };
197    }
198
199
200    private Callable<ProjectSegment> createCallableForBuildingOneFullModule( final ReactorContext reactorContext,
201                                                                             final MavenSession rootSession,
202                                                                             final ReactorBuildStatus reactorBuildStatus,
203                                                                             final MavenExecutionPlan executionPlan,
204                                                                             final ProjectSegment projectBuild,
205                                                                             final DependencyContext dependencyContext,
206                                                                             final ConcurrentBuildLogger concurrentBuildLogger )
207    {
208        return new Callable<ProjectSegment>()
209        {
210            public ProjectSegment call()
211                throws Exception
212            {
213                Iterator<ExecutionPlanItem> planItems = executionPlan.iterator();
214                ExecutionPlanItem current = planItems.hasNext() ? planItems.next() : null;
215                ThreadLockedArtifact threadLockedArtifact =
216                    (ThreadLockedArtifact) projectBuild.getProject().getArtifact();
217                if ( threadLockedArtifact != null )
218                {
219                    threadLockedArtifact.attachToThread();
220                }
221                long buildStartTime = System.currentTimeMillis();
222
223                //muxer.associateThreadWithProjectSegment( projectBuild );
224
225                if ( reactorBuildStatus.isHaltedOrBlacklisted( projectBuild.getProject() ) )
226                {
227                    eventCatapult.fire( ExecutionEvent.Type.ProjectSkipped, projectBuild.getSession(), null );
228                    return null;
229                }
230
231                eventCatapult.fire( ExecutionEvent.Type.ProjectStarted, projectBuild.getSession(), null );
232
233                Collection<ArtifactLink> dependencyLinks = getUpstreamReactorDependencies( projectBuild );
234
235                try
236                {
237                    PhaseRecorder phaseRecorder = new PhaseRecorder( projectBuild.getProject() );
238                    long totalMojoTime = 0;
239                    long mojoStart;
240                    while ( current != null && !reactorBuildStatus.isHaltedOrBlacklisted( projectBuild.getProject() ) )
241                    {
242
243                        BuildLogItem builtLogItem =
244                            concurrentBuildLogger.createBuildLogItem( projectBuild.getProject(), current );
245                        final Schedule schedule = current.getSchedule();
246
247                        mojoStart = System.currentTimeMillis();
248                        buildExecutionPlanItem( current, phaseRecorder, schedule, reactorContext, projectBuild,
249                                                dependencyContext );
250                        totalMojoTime += ( System.currentTimeMillis() - mojoStart );
251
252                        current.setComplete();
253                        builtLogItem.setComplete();
254
255                        ExecutionPlanItem nextPlanItem = planItems.hasNext() ? planItems.next() : null;
256                        if ( nextPlanItem != null && phaseRecorder.isDifferentPhase( nextPlanItem.getMojoExecution() ) )
257                        {
258
259                            final Schedule scheduleOfNext = nextPlanItem.getSchedule();
260                            if ( scheduleOfNext == null || !scheduleOfNext.isParallel() )
261                            {
262                                waitForAppropriateUpstreamExecutionsToFinish( builtLogItem, nextPlanItem, projectBuild,
263                                                                              scheduleOfNext );
264                            }
265
266                            for ( ArtifactLink dependencyLink : dependencyLinks )
267                            {
268                                dependencyLink.resolveFromUpstream();
269                            }
270                        }
271                        current = nextPlanItem;
272                    }
273
274                    final BuildSuccess summary =
275                        new BuildSuccess( projectBuild.getProject(), totalMojoTime ); // - waitingTime
276                    reactorContext.getResult().addBuildSummary( summary );
277                    eventCatapult.fire( ExecutionEvent.Type.ProjectSucceeded, projectBuild.getSession(), null );
278                }
279                catch ( Exception e )
280                {
281                    builderCommon.handleBuildError( reactorContext, rootSession, projectBuild.getSession(),
282                                                    projectBuild.getProject(), e, buildStartTime );
283                }
284                finally
285                {
286                    if ( current != null )
287                    {
288                        executionPlan.forceAllComplete();
289                    }
290                    // muxer.setThisModuleComplete( projectBuild );
291                }
292                return null;
293            }
294
295        };
296    }
297
298    private void waitForAppropriateUpstreamExecutionsToFinish( BuildLogItem builtLogItem,
299                                                               ExecutionPlanItem nextPlanItem,
300                                                               ProjectSegment projectBuild, Schedule scheduleOfNext )
301        throws InterruptedException
302    {
303        for ( MavenProject upstreamProject : projectBuild.getImmediateUpstreamProjects() )
304        {
305            final MavenExecutionPlan upstreamPlan = executionPlans.get( upstreamProject );
306            final String nextPhase = scheduleOfNext != null && scheduleOfNext.hasUpstreamPhaseDefined()
307                ? scheduleOfNext.getUpstreamPhase()
308                : nextPlanItem.getLifecyclePhase();
309            final ExecutionPlanItem upstream = upstreamPlan.findLastInPhase( nextPhase );
310
311            if ( upstream != null )
312            {
313                long startWait = System.currentTimeMillis();
314                upstream.waitUntilDone();
315                builtLogItem.addWait( upstreamProject, upstream, startWait );
316            }
317            else if ( !upstreamPlan.containsPhase( nextPhase ) )
318            {
319                // Still a bit of a kludge; if we cannot connect in a sensible way to
320                // the upstream build plan we just revert to waiting for it all to
321                // complete. Real problem is per-mojo phase->lifecycle mapping
322                builtLogItem.addDependency( upstreamProject, "No phase tracking possible " );
323                upstreamPlan.waitUntilAllDone();
324            }
325            else
326            {
327                builtLogItem.addDependency( upstreamProject, "No schedule" );
328            }
329        }
330    }
331
332    private Collection<ArtifactLink> getUpstreamReactorDependencies( ProjectSegment projectBuild )
333    {
334        Collection<ArtifactLink> result = new ArrayList<ArtifactLink>();
335        for ( MavenProject upstreamProject : projectBuild.getTransitiveUpstreamProjects() )
336        {
337            Artifact upStreamArtifact = upstreamProject.getArtifact();
338            if ( upStreamArtifact != null )
339            {
340                Artifact dependencyArtifact = findDependency( projectBuild.getProject(), upStreamArtifact );
341                if ( dependencyArtifact != null )
342                {
343                    result.add( new ArtifactLink( dependencyArtifact, upStreamArtifact ) );
344                }
345            }
346
347            Artifact upStreamTestScopedArtifact = findTestScopedArtifact( upstreamProject );
348            if ( upStreamTestScopedArtifact != null )
349            {
350                Artifact dependencyArtifact = findDependency( projectBuild.getProject(), upStreamArtifact );
351                if ( dependencyArtifact != null )
352                {
353                    result.add( new ArtifactLink( dependencyArtifact, upStreamTestScopedArtifact ) );
354                }
355            }
356        }
357        return result;
358    }
359
360
361    private Artifact findTestScopedArtifact( MavenProject upstreamProject )
362    {
363        if ( upstreamProject == null )
364        {
365            return null;
366        }
367
368        List<Artifact> artifactList = upstreamProject.getAttachedArtifacts();
369        for ( Artifact artifact : artifactList )
370        {
371            if ( Artifact.SCOPE_TEST.equals( artifact.getScope() ) )
372            {
373                return artifact;
374            }
375        }
376        return null;
377    }
378
379    private static boolean isThreadLockedAndEmpty( Artifact artifact )
380    {
381        return artifact instanceof ThreadLockedArtifact && !( (ThreadLockedArtifact) artifact ).hasReal();
382    }
383
384    private static Artifact findDependency( MavenProject project, Artifact upStreamArtifact )
385    {
386        if ( upStreamArtifact == null || isThreadLockedAndEmpty( upStreamArtifact ) )
387        {
388            return null;
389        }
390
391        String key = ArtifactUtils.key( upStreamArtifact.getGroupId(), upStreamArtifact.getArtifactId(),
392                                        upStreamArtifact.getVersion() );
393        final Set<Artifact> deps = project.getDependencyArtifacts();
394        for ( Artifact dep : deps )
395        {
396            String depKey = ArtifactUtils.key( dep.getGroupId(), dep.getArtifactId(), dep.getVersion() );
397            if ( key.equals( depKey ) )
398            {
399                return dep;
400            }
401        }
402        return null;
403
404    }
405
406    private void buildExecutionPlanItem( ExecutionPlanItem current, PhaseRecorder phaseRecorder, Schedule schedule,
407                                         ReactorContext reactorContext, ProjectSegment projectBuild,
408                                         DependencyContext dependencyContext )
409        throws LifecycleExecutionException
410    {
411        if ( schedule != null && schedule.isMojoSynchronized() )
412        {
413            synchronized ( current.getPlugin() )
414            {
415                buildExecutionPlanItem( reactorContext, current, projectBuild, dependencyContext, phaseRecorder );
416            }
417        }
418        else
419        {
420            buildExecutionPlanItem( reactorContext, current, projectBuild, dependencyContext, phaseRecorder );
421        }
422    }
423
424
425    private void buildExecutionPlanItem( ReactorContext reactorContext, ExecutionPlanItem node,
426                                         ProjectSegment projectBuild, DependencyContext dependencyContext,
427                                         PhaseRecorder phaseRecorder )
428        throws LifecycleExecutionException
429    {
430
431        MavenProject currentProject = projectBuild.getProject();
432
433        long buildStartTime = System.currentTimeMillis();
434
435        CurrentPhaseForThread.setPhase(  node.getLifecyclePhase() );
436
437        MavenSession sessionForThisModule = projectBuild.getSession();
438        try
439        {
440
441            if ( reactorContext.getReactorBuildStatus().isHaltedOrBlacklisted( currentProject ) )
442            {
443                return;
444            }
445
446            BuilderCommon.attachToThread( currentProject );
447
448            mojoExecutor.execute( sessionForThisModule, node.getMojoExecution(), reactorContext.getProjectIndex(),
449                                  dependencyContext, phaseRecorder );
450
451            final BuildSuccess summary =
452                new BuildSuccess( currentProject, System.currentTimeMillis() - buildStartTime );
453            reactorContext.getResult().addBuildSummary( summary );
454        }
455        finally
456        {
457            Thread.currentThread().setContextClassLoader( reactorContext.getOriginalContextClassLoader() );
458        }
459    }
460
461    public static boolean isWeaveMode( MavenExecutionRequest request )
462    {
463        return "true".equals( request.getUserProperties().getProperty( "maven3.weaveMode" ) );
464    }
465
466    public static void setWeaveMode( Properties properties )
467    {
468        properties.setProperty( "maven3.weaveMode", "true" );
469    }
470
471    static class ArtifactLink
472    {
473        private final Artifact artifactInThis;
474
475        private final Artifact upstream;
476
477        ArtifactLink( Artifact artifactInThis, Artifact upstream )
478        {
479            this.artifactInThis = artifactInThis;
480            this.upstream = upstream;
481        }
482
483        public void resolveFromUpstream()
484        {
485            artifactInThis.setFile( upstream.getFile() );
486            artifactInThis.setRepository( upstream.getRepository() );
487            artifactInThis.setResolved( true ); // Or maybe upstream.isResolved()....
488        }
489    }
490}